番茄(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(); ... } }
|
如果他们称我为遵循此架构的“代码猴子”怎么办?
别理他们。专注于提供业务价值。