番茄架构:一种遵循常识宣言的软件架构方法


番茄(tomato)架构是一种遵循常识宣言的软件架构方法:

1、将业务逻辑执行与输入源(Web 控制器、消息监听器、计划作业等)分开
Web 控制器、消息监听器、计划作业等输入源应该是一个薄层,从请求中提取数据并将实际业务逻辑执行委托给“应用程序核心”。

不要这样做:

@RestController
class CustomerController {
    private final CustomerService customerService;
    
    @PostMapping("/api/customers")
    void createCustomer(@RequestBody Customer customer) {
       if(customerService.existsByEmail(customer.getEmail())) {
           throw new EmailAlreadyInUseException(customer.getEmail());
       }
       customer.setCreateAt(Instant.now());
       customerService.save(customer);
    }
}

相反,这样做:

@RestController
class CustomerController {
    private final CustomerService customerService;
    
    @PostMapping("/api/customers")
    void createCustomer(@RequestBody Customer customer) {
       customerService.save(customer);
    }
}

@Service
@Transactional
class CustomerService {
   private final CustomerRepository customerRepository;

   void save(Customer customer) {
      if(customerRepository.existsByEmail(customer.getEmail())) {
         throw new EmailAlreadyInUseException(customer.getEmail());
      }
      customer.setCreateAt(Instant.now());
      customerRepository.save(customer);
   }
}

使用这种方法,无论您是尝试从 REST API 调用还是从 CLI 创建客户,所有业务逻辑都集中在 Application Core应用程序核心 中。

2、不要让“外部服务集成”对“应用程序核心”的影响太大
从应用程序核心中,我们可以与数据库、消息代理或第 3 方 Web 服务等进行对话。必须注意业务逻辑执行者不要严重依赖外部服务集成。

例如,假设您正在使用 Spring Data JPA 进行持久化,并且您希望使用分页从CustomerService获取客户。

不要这样做

@Service
@Transactional
class CustomerService {
   private final CustomerRepository customerRepository;

   PagedResult<Customer> getCustomers(Integer pageNo) {
      Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.of("name"));
      Page<Customer> cusomersPage = customerRepository.findAll(pageable);
      return convertToPagedResult(cusomersPage);
   }
}

相反,这样做

@Service
@Transactional
class CustomerService {
   private final CustomerRepository customerRepository;

   PagedResult<Customer> getCustomers(Integer pageNo) {
      return customerRepository.findAll(pageNo);
   }
}

@Repository
class JpaCustomerRepository {

   PagedResult<Customer> findAll(Integer pageNo) {
      Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.of("name"));
      return ...;
   }
}

这样,任何持久性库更改都只会影响存储库层。

3、将领域逻辑保留在领域对象中
有影响领域对象状态更改方法或从对象状态计算某些内容的方法,则将这些方法放入该域对象。

不要这样做

class Cart {
    List<LineItem> items;
}

@Service
@Transactional
class CartService {

   CartDTO getCart(UUID cartId) {
      Cart cart = cartRepository.getCart(cartId);
      BigDecimal cartTotal = this.calculateCartTotal(cart);
      ...
   }
   
   private BigDecimal calculateCartTotal(Cart cart) {
      ...
   }
}


相反,这样做:

class Cart {
    List<LineItem> items;

   public BigDecimal getTotal() {
      ...
   }
}

@Service
@Transactional
class CartService {

   CartDTO getCart(UUID cartId) {
      Cart cart = cartRepository.getCart(cartId);
      BigDecimal cartTotal = cart.getTotal();
      ...
   }
}


如果他们称我为遵循此架构的“代码猴子”怎么办?
别理他们。专注于提供业务价值。