业务代表模式:让客户端调用变得优雅

业务代表模式(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