一,前言
之所以写这些文章,很大程度上,是因为阅读了阿里技术专家的文章,读完之后,对我内心触动很大,文章多出引用了阿里技术文章的内容,仅作为个人学习用途,也是作为技术人,希望技术可以被更多人学习到。
作为一个实习生,谈架构,未免让人感觉,好高骛远,以下陈述只属于个人浅薄意见。
依个人浅薄意见,架构的本质就是应用的拆分和聚合。
拆分也就是应用微服务化,聚合,并不是指将多个微服务聚合成一个应用,而是指聚合多个微服务的功能,让他完成一个大的功能。这样做的灵活性,可扩展性就会更高。
所谓应用架构,个人理解,更可以指的是应用中固定不变的代码结构,设计模式,规范和组件间的通信。
一个好的应用架构,通过规定一套规范,可以让团队内能力参差不齐的开发人员更好的共同开发,降低开发成本,提升开发效率和代码质量。
要求,或者原则?
- 独立于框架:架构不应该依赖某个外部的库或框架,不应该被框架的结构所束缚。
- 独立于前端:前台展示的样式可能会随时发生变化(今天可能是网页、明天可能变成console、后天是独立app),但是底层架构不应该随之而变化。
- 独立于底层数据源:无论今天你用MySQL、Oracle还是MongoDB、CouchDB,甚至使用文件系统,软件架构不应该因为不同的底层数据储存方式而产生巨大改变。
- 独立于外部依赖:无论外部依赖如何变更、升级,业务的核心逻辑不应该随之而大幅变化。
- 测试覆盖率:无论外部依赖了什么数据库、硬件、UI或者服务,业务的逻辑应该都能够快速被验证正确性。
通过一个需求,来论证。
用户可以通过银行网页转账给另一个账号,支持跨币种转账。同时因为监管和对账需求,需要记录本次转账活动。
二,传统互联网架构下的架构设计
1、从MySql数据库中找到转出和转入的账户,选择用 MyBatis 的 mapper 实现 DAO;
2、从 Yahoo(或其他渠道)提供的汇率服务获取转账的汇率信息(底层是 http 开放接口);
3、计算需要转出的金额,确保账户有足够余额,并且没超出每日转账上限;
4、实现转入和转出操作,扣除手续费,保存数据库;
5、发送 Kafka 审计消息,以便审计和对账用;
public class TransferController {
private TransferService transferService;
public Result<Boolean> transfer(String targetAccountNumber, BigDecimal amount, HttpSession session) {
Long userId = (Long) session.getAttribute("userId");
return transferService.transfer(userId, targetAccountNumber, amount, "CNY");
}
}
public class TransferServiceImpl implements TransferService {
private static final String TOPIC_AUDIT_LOG = "TOPIC_AUDIT_LOG";
private AccountMapper accountDAO;
private KafkaTemplate<String, String> kafkaTemplate;
private YahooForexService yahooForex;
@Override
public Result<Boolean> transfer(Long sourceUserId, String targetAccountNumber, BigDecimal targetAmount, String targetCurrency) {
// 1. 从数据库读取数据,忽略所有校验逻辑如账号是否存在等
AccountDO sourceAccountDO = accountDAO.selectByUserId(sourceUserId);
AccountDO targetAccountDO = accountDAO.selectByAccountNumber(targetAccountNumber);
// 2. 业务参数校验
if (!targetAccountDO.getCurrency().equals(targetCurrency)) {
throw new InvalidCurrencyException();
}
// 3. 获取外部数据,并且包含一定的业务逻辑
// exchange rate = 1 source currency = X target currency
BigDecimal exchangeRate = BigDecimal.ONE;
if (sourceAccountDO.getCurrency().equals(targetCurrency)) {
exchangeRate = yahooForex.getExchangeRate(sourceAccountDO.getCurrency(), targetCurrency);
}
BigDecimal sourceAmount = targetAmount.divide(exchangeRate, RoundingMode.DOWN);
// 4. 业务参数校验
if (sourceAccountDO.getAvailable().compareTo(sourceAmount) < 0) {
throw new InsufficientFundsException();
}
if (sourceAccountDO.getDailyLimit().compareTo(sourceAmount) < 0) {
throw new DailyLimitExceededException();
}
// 5. 计算新值,并且更新字段
BigDecimal newSource = sourceAccountDO.getAvailable().subtract(sourceAmount);
BigDecimal newTarget = targetAccountDO.getAvailable().add(targetAmount);
sourceAccountDO.setAvailable(newSource);
targetAccountDO.setAvailable(newTarget