假设我们有一个端口/适配器模式的典型实现:
@RestController
private class OrderController {
private final CreateOrderPort createOrderPort;
@PostMapping
void createOrder(String customerId) {
createOrderPort.createOrder(customerId);
}
}
private class KafkaConsumer {
private final CreateOrderPort createOrderPort;
@PostMapping
public void createOrder(CreateOrderEvent event) {
createOrderPort.createOrder(event.getCustomerId());
}
}
public interface CreateOrderPort {
void createOrder(String customerId);
}
@Service
public class CreateOrderService implements CreateOrderPort {
public void createOrder(String customerId){
orderRepository.save(new Order(customerId));
}
}
我只是想知道只有一个界面的确切好处是什么? 如果Kafka消费者和控制器直接使用CreateOrderService会有什么区别?
@RestController
private class OrderController {
private final CreateOrderService createOrderService;
@PostMapping
void createOrder(String customerId) {
createOrderService.createOrder(customerId);
}
}
private class KafkaConsumer {
private final CreateOrderService createOrderService;
@PostMapping
public void createOrder(CreateOrderEvent event) {
createOrderService.createOrder(event.getCustomerId());
}
}
@Service
public class CreateOrderService{
public void createOrder(String customerId){
orderRepository.save(new Order(customerId));
}
}
我们仍然将技术细节(适配器或边界)和业务细节(核心或领域)分开。如果服务发生变化,就会对技术层产生任何影响。
我只是想知道只有一个界面的确切好处是什么?如果Kafka消费者和控制器直接使用CreateOrderService会有什么区别?
您可以更轻松地测试它,因为创建模拟所需的精力更少。
class OrderControllerTest {
private String customerId;
@Test
void createOrder() {
CreateOrderPort createOrderPortMock = customerId -> OrderControllerTest.this.customerId = customerId;
OrderController orderController = new OrderController(createOrderPortMock);
orderController.createOrder("1234");
assertEquals("1234", customerId);
}
}
在更复杂的场景中,您可能想要创建一个实现输入端口的模拟类 或者您可能想创建一个专门的类以提高可读性。
class OrderControllerTest {
class CreateOrderPortMock implements CreateOrderPort {
private String customerId;
@Override
public void createOrder(String customerId) {
this.customerId = customerId;
}
public void assertCreateOrderInvoked(String expectedCustomerId){
assertEquals(expectedCustomerId, this.customerId);
}
}
@Test
void createOrder() {
CreateOrderPortMock createOrderPortMock = new CreateOrderPortMock();
OrderController orderController = new OrderController(createOrderPortMock);
orderController.createOrder("1234");
createOrderPortMock.assertCreateOrderInvoked("1234");
}
}
当然,有人可能会争辩说,您也可以扩展实际的实现并重写该方法。但是这个 可能会很棘手,因为真正的实现需要构造函数参数才能创建。什么时候 你使用真正的实现,你必须处理真正的实现依赖关系。
public class CreateOrderService implements CreateOrderPort {
private OrderRepository orderRepository;
public CreateOrderService(OrderRepository orderRepository) {
this.orderRepository = Objects.requireNonNull(orderRepository);
}
public void createOrder(String customerId){
orderRepository.save(new Order(customerId));
}
}
如果真正的实现确实检查构造函数或执行一些逻辑,则需要满足所有这些 创建模拟以覆盖 createOrder 方法的先决条件。如果您这样做,您的测试就会受到限制 所有这些先决条件。这意味着即使它与它所做的事情无关,它也会破产 它打破了。例如。如果有人更改构造函数逻辑,您的“创建订单控制器”测试就会中断。
最后,如果您使用接口,则不需要使用 cglib 代理或其他任何东西的花哨的模拟框架 只是模拟简单的输入端口。因此,如果更新某些库(和传递依赖项),您的依赖项就会减少,麻烦也会减少。