
核心结论: Java 架构师的核心能力不是"会更多框架",而是能在业务不确定的前提下,用最小的约束成本划出系统边界。写代码解决的是"怎么实现",定架构解决的是"什么不该做、什么必须隔离、什么允许以后改"。前者是技术,后者是判断。
高级开发的评价标准是"能搞定复杂需求",架构师的评价标准是"让复杂需求不会同时压垮系统"。两者的差异体现在三个决策上:
决策维度 | 高级开发 | 架构师 |
|---|---|---|
关注点 | 本次功能怎么实现 | 这个能力三年后还能不能改 |
边界 | 模块内代码质量 | 模块间依赖方向 |
变更成本 | 改代码要多久 | 改架构要动几个团队 |
技术选型 | 哪个框架好用 | 哪个约束代价最低 |
一个典型的架构失手案例:业务早期为了"快速上线",让订单模块直接调用库存模块的 Mapper。半年后库存要拆成独立服务,发现订单里嵌了几十处库存表查询。省下的是当初两天的接口设计,付出的是后来两个月的重构。
架构师最常用的工具是依赖倒置。以下是一个订单创建场景的骨架,核心是把"订单依赖库存"改成"订单依赖库存的能力接口":
// 订单模块定义自己需要的能力,不关心谁实现
public interface StockService {
boolean tryLock(String skuId, int qty);
void release(String skuId, int qty);
}
// 订单服务只依赖接口
@Service
public class OrderService {
private final StockService stockService;
public OrderService(StockService stockService) {
this.stockService = stockService;
}
@Transactional
public Order create(CreateOrderCmd cmd) {
boolean locked = stockService.tryLock(cmd.getSkuId(), cmd.getQty());
if (!locked) {
throw new BizException("库存不足");
}
try {
Order order = doCreate(cmd);
return order;
} catch (Exception e) {
stockService.release(cmd.getSkuId(), cmd.getQty());
throw e;
}
}
}库存的实现放在基础设施层,通过 Spring 注入:
@Repository
public class DbStockService implements StockService {
private final StockMapper mapper;
@Override
public boolean tryLock(String skuId, int qty) {
return mapper.decrease(skuId, qty) > 0; // 乐观锁扣减
}
@Override
public void release(String skuId, int qty) {
mapper.increase(skuId, qty);
}
}这个设计的价值不在"看起来解耦",而在变更可控:库存从数据库换成 Redis、从单体拆成远程服务、从同步改成异步预占,订单模块一行都不用改。架构师定的是这个约束,不是具体实现。
架构师最容易犯的错误是承诺"分布式事务保证一致性"。在真实系统里,跨服务的强一致性代价极高,工程上通常用最终一致性 + 补偿替代。以下是本地消息表的核心逻辑:
@Service
public class OrderTxService {
private final OrderMapper orderMapper;
private final MessageMapper messageMapper;
@Transactional
public void createWithEvent(CreateOrderCmd cmd) {
Order order = buildOrder(cmd);
orderMapper.insert(order);
// 事件与业务在同一事务内落库,保证不丢
messageMapper.insert(new Message(
"ORDER_CREATED", order.getId(), "PENDING"));
}
}
// 独立调度器扫描 PENDING 消息投递
@Scheduled(fixedDelay = 3000)
public void dispatch() {
for (Message m : messageMapper.findPending(100)) {
try {
mqProducer.send(m);
messageMapper.markSent(m.getId());
} catch (Exception e) {
messageMapper.markFailed(m.getId());
}
}
}架构师在这个场景要做三个判断:是否允许短暂不一致(允许)、失败后能否人工介入(能)、补偿逻辑是否幂等(必须)。这三个判断比选哪个 MQ 重要得多。
架构师常被批评"过度设计"。判断一个抽象该不该加,看它是否有明确的变更预期。以下是一个配置读取的演进示例:
// 第一版:直接读配置,够用
@Service
public class RateLimitService {
@Value("${rate.limit.qps:100}")
private int qps;
}
// 需要支持按租户动态调整时,才引入策略接口
public interface RateLimitPolicy {
int qpsOf(String tenantId);
}
@Service
public class ConfigurableRateLimitService {
private final RateLimitPolicy policy;
public boolean allow(String tenantId) {
return counter.tryAcquire(tenantId, policy.qpsOf(tenantId));
}
}第一版直接读配置是对的——那个阶段没有多租户需求。第二版引入接口也是对的——需求出现了,抽象有了明确边界。没有变更预期的抽象是负债,有变更预期的抽象是资产。
第一,依赖方向比依赖数量重要。 业务层可以依赖基础设施层的接口,基础设施层不能反向依赖业务。方向错了,模块再小也会烂。
第二,事务边界要诚实。 能本地事务就别分布式,能最终一致就别强一致。架构师的价值是给出可落地的方案,不是给出最完美的方案。
第三,抽象跟着变更走。 没有明确的变更预期,不加接口层。加了就要维护,维护成本也是成本。
第四,留文档不如留约束。 把依赖规则写进 CI(比如 ArchUnit 测试),比写在文档里更可靠:
@ArchTest
static final ArchRule layerRule = layeredArchitecture()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Infra").definedBy("..infra..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller");Java 架构师的能力分水岭,不在于会用多少框架,而在于能否在业务压力下守住边界。写代码的人决定系统今天能不能跑,定架构的人决定系统明年还能不能改。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。