业务代表模式:让客户端调用变得优雅
业务代表模式(Business Delegate),说白了就是给客户端加一层“中间人”。
我最早接触这个模式是在做企业级应用的时候。那时候EJB还是主流,客户端要调用远程业务服务,代码里全是JNDI查找、异常处理、重试逻辑……写起来特别痛苦。后来我发现,其实可以封装一层,让客户端只关心“我要做什么”,而不关心“怎么找到服务”。
嗯,这就是业务代表模式的核心思想。
模式结构:三个角色一台戏
业务代表模式由三个核心角色组成:
- 业务服务(Business Service):真正的业务逻辑实现
- 查找服务(Lookup Service):负责定位和获取业务服务实例
- 业务委托(Business Delegate):客户端的代理,封装查找和调用细节
你想想看,这就像你去餐厅吃饭。你(客户端)不需要知道厨师在哪、怎么联系他。你只需要告诉服务员(业务委托),服务员会去后厨(查找服务)找到对应的厨师(业务服务),然后把菜端上来。
核心价值:业务代表模式将客户端与业务服务解耦,客户端不需要知道服务的具体位置、通信协议、查找方式等细节。
SVG架构图:一目了然的关系
Java代码示例:从EJB到微服务
先看一个经典的EJB场景。我记得当年写EJB客户端时,代码里全是这样的模板:
// 没有业务代表模式时,客户端代码又臭又长
public class OrderClient {
public void placeOrder(Order order) {
try {
Context ctx = new InitialContext();
OrderServiceRemote service =
(OrderServiceRemote) ctx.lookup("java:global/OrderService");
service.createOrder(order);
} catch (NamingException e) {
// 各种异常处理
}
}
}
用了业务代表模式之后,清爽多了:
// 业务委托:封装所有查找和调用细节
public class OrderServiceDelegate {
private static final String JNDI_NAME = "java:global/OrderService";
public void createOrder(Order order) {
OrderServiceRemote service = lookupService();
service.createOrder(order);
}
private OrderServiceRemote lookupService() {
try {
Context ctx = new InitialContext();
return (OrderServiceRemote) ctx.lookup(JNDI_NAME);
} catch (NamingException e) {
throw new ServiceException("无法定位订单服务", e);
}
}
}
// 客户端代码变得极其简单
public class OrderClient {
private OrderServiceDelegate delegate = new OrderServiceDelegate();
public void placeOrder(Order order) {
delegate.createOrder(order);
}
}
我的经验:我在一个老项目中接手过这样的代码——每个业务方法里都重复写JNDI查找。后来我提取了一个通用的业务委托基类,把查找逻辑统一管理,代码量直接减少了40%。
微服务场景下的应用
到了微服务时代,业务代表模式依然好用。只不过查找服务从JNDI变成了服务注册中心(比如Consul、Eureka)。
// 微服务版本的业务委托
public class UserServiceDelegate {
private final ServiceDiscoveryClient discoveryClient;
public UserServiceDelegate(ServiceDiscoveryClient client) {
this.discoveryClient = client;
}
public User getUserById(String userId) {
// 通过服务发现获取服务实例
ServiceInstance instance = discoveryClient
.getInstances("user-service")
.stream()
.findFirst()
.orElseThrow(() -> new ServiceNotFoundException("user-service"));
// 构建REST客户端调用
RestTemplate rest = new RestTemplate();
String url = String.format("http://%s:%d/users/%s",
instance.getHost(), instance.getPort(), userId);
return rest.getForObject(url, User.class);
}
}
避坑指南:我曾经踩过的坑
我曾经犯过一个错误:把业务委托做得太重,里面塞了缓存、重试、熔断、日志……结果这个类变得又大又难维护。
后来我明白了,业务委托的核心职责是“封装查找和调用”,其他功能应该通过装饰器或AOP来实现。
还有几个常见的坑:
- 不要暴露查找细节:业务委托的接口应该和业务服务保持一致,不要暴露JNDI名称、服务地址等细节
- 注意异常转换:把底层异常(如网络超时、服务不可用)转换成业务异常,不要让客户端处理技术异常
- 考虑缓存查找结果:如果服务实例不频繁变化,可以缓存查找结果,避免每次都去注册中心查询
模式对比:什么时候用?
| 场景 | 是否推荐使用 | 原因 |
|---|---|---|
| 客户端直接调用远程服务 | ✅ 强烈推荐 | 封装复杂性,降低耦合 |
| 服务地址频繁变化 | ✅ 推荐 | 查找服务统一管理地址 |
| 客户端和服务在同一进程 | ⚠️ 谨慎使用 | 可能过度设计,直接注入即可 |
| 需要多种协议支持 | ✅ 推荐 | 委托层可以封装协议转换 |
总结一下
业务代表模式不是什么高深的东西,但它解决了一个很实际的问题:让客户端调用变得简单、优雅。
我个人习惯在以下场景优先考虑这个模式:
- 客户端需要调用远程服务,且服务地址可能变化
- 多个客户端复用相同的服务查找逻辑
- 需要统一处理异常、重试、超时等横切关注点
说白了,就是给客户端减负。让客户端只关心业务,把技术细节交给业务委托去处理。
嗯,这就是业务代表模式。简单,但实用。
公众号:蓝海资料掘金营,微信deep3321