聊聊Tomato Architecture
序
本文主要研究一下Tomato Architecture
Clean/Onion/Hexagonal/Ports&Adapters Architectures
Clean Architecture
clean architecture定义了四层结构,最内层是entities(enterprise business rules
),再往外是use cases(application business rules
),接着是interface adapters(比如controller、presenters、gateways
),最外层是frameworks & drivers(比如web、ui、db、devices、external interfaces
)
clean architecture主要是分了4层结构,domain层,有的会把repository接口放在这一层,然后domain service会调用repository;use case层对应ddd的application层,主要是业务编排,有的也把repository接口放在这一层;interfaces adapters层会对输入和输出进行适配,实现use case定义的方法,类似ddd的interfaces层;infrastructure层主要是对基础服务/类库的管理,有些工程把对repository的实现也放这里了,貌似不太妥当。
Onion Architecture
Onion Architecture定义了domain、repository、services、ui这几层,其核心要点如下:
- 整个应用基于独立的domain构建
- 内部的layer定义接口,外部的layer实现接口
- 内层与外层通过接口解耦
- services(business logic)可以独立于infrastructure编译和运行
Onion Architecture的核心在于内层定义接口,外层来进行实现,然后业务逻辑层则是基于接口来实现业务逻辑,基于接口来进行解耦。
Hexagonal Architecture(Ports&Adapters Architecture
)
Ports and Adapters architecture,又叫Hexagonal architecture,其中ports层是六边形的边界,其中port又可以分为driver port及driven port,简单理解对应输入层及输出层;边界保护的是内部的app,其中app包括use cases或者叫做application services层以及domain层;adapter可以理解为将外部依赖进行适配,实现port层定义的接口。
buckpal工程分了adapter、application、domain三层;其中application层定义了port包,该包定义了in、out两种类型的接口;adapter层也分in、out两类,分别实现application/port层的接口;application的service则实现了port的接口。其中domain层不依赖任何层;application层的port定义了接口,然后service层实现接口和引用接口;adapter层则实现了application的port层的接口。
Tomato Architecture
既往架构评论
There is No Silver Bullet Architecture
Clean / Onion / Hexagonal / Ports&Adapters Architectures都不能解决所有问题
Wrong pursuit of Testability
想通过这些架构的抽象来使得单元测试不需要依赖外部服务(数据库、MQ、定时任务等)有点不接地气,现实的企业级服务代码经常是重度依赖这些外部服务的,而且即使是这么做,无论有多少单元测试在没有集成测试的时候也没没有信心保证代码没有问题
Tomato Architecture倾向于移除这些抽象,直接写更多的集成测试来保证代码的质量
Simplicity and Readability Wins in the Long Run
简单才是王道,这样子后续可维护性更强
Key Principles
- 思考哪些适合自己而不是盲从大众的建议
- 保持简单而不是过度设计
- 直接开干,没有必要不过度抽象想着替换
- 确保方案在整体而不是局部适用
Architecture Diagram
Implementation Guidelines
Package by feature
对于构建单体或者模块化单体,强烈建议通过feature来分包,而不是技术层面的分层
Keep “Application Core” independent of delivery mechanism (Web, Scheduler Jobs, CLI)
Application Core中剥离Web, Scheduler Jobs, CLI这些服务提供方式
Separate the business logic execution from input sources (Web Controllers, Message Listeners, Scheduled Jobs etc)
Web Controllers, Message Listeners, Scheduled Jobs这些层不要包含业务逻辑
不建议:
@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);}
}
Don’t let the “External Service Integrations” influence the “Application Core” too much
尽可能少让外部服务代码渗透到Application Core逻辑中,确保Application Core尽可能少依赖这些外部服务
不建议
@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 ...;}
}
Keep domain logic in domain objects
尽可能把一些领域逻辑封装在domain objects中,比如不建议:
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();...}
}
No unnecessary interfaces
不要一上来就定义接口,以期望说哪天会有其他实现,如果是说方便单测,但事实上现在有很多框架可以进行mock,可以不需要定义接口。所以如无必要不定义接口。
Embrace the framework’s power and flexibility
可以直接拥抱框架,不需要在这之前试图再抽象一层以图说后续要切换,一般这类需求不多,直接用框架的能力就好
Test not only units, but whole features
通过mock去跑单元测试是有必要,但是它没办法验证替代集成测试,所以借助注入testcontainers来直接进行集成测试更能提升对代码的信心
小结
Tomato Architecture在实践的基础上对Clean/Onion/Hexagonal/Ports&Adapters Architectures进行了改良,大的原则还是不变,即interface层不渗透到业务层,业务层代码也不泄露到interface层。
改良的部分是:
- 业务层尽量少依赖外部服务层
- 如无多个实现则少定义接口,利用框架能力进行mock
- 少在框架层上进行抽象以试图后续切换,一般大的框架比较少有切换需求
- 单元测试不如集成测试实在
doc
- Tomato Architecture - A Pragmatic Approach to Software Design
- Package by Feature
- 聊聊golang的clean architecture项目结构
- 聊聊go-bank-transfer项目对Clean Architecture的实践
- 聊聊Onion Architecture项目结构
- www.hexagonalarchitecture.org
- 聊聊buckpal对于Hexagonal Architecture的实践
- 聊聊Ports and Adapters architecture
相关文章:

聊聊Tomato Architecture
序 本文主要研究一下Tomato Architecture Clean/Onion/Hexagonal/Ports&Adapters Architectures Clean Architecture clean architecture定义了四层结构,最内层是entities(enterprise business rules),再往外是use cases(application business ru…...

小白的进阶之路系列之十二----人工智能从初步到精通pytorch综合运用的讲解第五部分
在本笔记本中,我们将针对Fashion-MNIST数据集训练LeNet-5的变体。Fashion-MNIST是一组描绘各种服装的图像瓦片,有十个类别标签表明所描绘的服装类型。 # PyTorch model and training necessities import torch import torch.nn as nn import torch.nn.functional as F impor…...
Java并发编程实战 Day 6:Future与异步编程模型
【Java并发编程实战 Day 6】Future与异步编程模型 在今天的课程中,我们将深入学习Java中的Future与异步编程模型。这是为期30天的"Java并发编程实战"系列的第6天。 理论基础 Future接口 Future接口是Java提供的用于表示异步计算的结果。它提供了以下方…...
.NET Core 中预防跨网站请求伪造 (XSRFCSRF) 攻击
.NET Core 中预防跨网站请求伪造 (XSRF/CSRF) 攻击 在如今的网络环境中,安全问题一直是开发者们不可忽视的重要方面。跨网站请求伪造(Cross-Site Request Forgery,简称 CSRF)就是一种常见且具有威胁性的网络攻击方式。攻击者通过…...
MFC Resource.h 文件详解与修改指南
MFC Resource.h 文件详解与修改指南 Resource.h 是 Visual C 和 MFC 项目中用于集中管理资源标识符(Resource ID)的头文件。它由 Visual Studio 的资源编辑器自动生成并维护,也可以手动编辑。理解并合理使用该文件对于管理对话框、控件、菜单…...

2025年06月03日Github流行趋势
项目名称:onlook 项目地址url:https://github.com/onlook-dev/onlook项目语言:TypeScript历史star数:12871今日star数:624项目维护者:Kitenite, drfarrell, spartan-vutrannguyen, apps/devin-ai-integrati…...
AI视频编码器(0.4.3) 调试训练bug——使用timm SoftTargetCrossEntropy时出现loss inf
检查过程 进入loss内部实现: > /root/miniconda3/envs/UMT2/lib/python3.9/site-packages/timm/loss/cross_entropy.py(35...

【数据分析】基于Cox模型的R语言实现生存分析与生物标志物风险评估
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理生存分析画图输出图片其他标记物的分析总结系统信息介绍 分析生存数据与多种生物标志物之间的关系。它通过Cox比例风险模型来评估不同生物标志物…...

使用nginx配置反向代理,负载均衡
首先啥叫反向代理 咋配置呢,那当然是在nginx目录下改conf文件了 具体咋改呢,那就新增一个新的server配置,然后在location里新增你想代理的服务器 实际上负载均衡也就是根据反向代理的思路来的,如下所示 配置的话实际上也与上…...

从 iPhone 备份照片: 保存iPhone图片的5种方法
随着智能手机越来越融入我们的生活,我们的照片已成为我们设备上最有价值的数据形式之一。然而,iPhone内部存储空间仍然有限,因此我们需要将iPhone中的照片备份到另一个地方,以释放空间并确保珍贵的图像记忆的安全。阅读本指南&…...

Spring Ai 从Demo到搭建套壳项目(一)初识与实现与deepseek对话模式
前言 为什么说Java长青,主要是因为其生态圈完善,Spring又做了一款脚手架,把对接各个LLM厂商的sdk做了一遍,形成一系列的spring-ai-starter-** 的依赖。 目前为止版本去到1.0.0.M6,golang跟不上了吧, Make …...

快速上手pytest
1. pytest的基础 1.1 什么是pytest pytest 是 python 中的一个测试框架,用于编写简洁、可扩展的测试代码,帮助开发者验证结果是否与预期相符。 python 中有很多的测试框架,那我们为什么要学习 pytest 呢? pytest 的优势&…...

设备驱动与文件系统:02 键盘
操作系统中键盘驱动的讲解 在这一讲中,我将为大家讲解键盘相关内容。从上一讲开始,我们进入了操作系统第四个部分的学习,也就是操作系统对设备的驱动与管理。 上一讲我们探讨的是显示器,并且提到,一个终端设备是由显示…...
matlab分布式电源接入对配电网的影响
MATLAB仿真的分布式电源接入对于配电网的影响,潮流计算加了固定的pq 分布式电源接入对配电网的影响/bustypes.m , 1004 分布式电源接入对配电网的影响/case34.m , 5385 分布式电源接入对配电网的影响/case9.m , 1366 分布式电源接入对配电网的影响/dSbus_dV.m , 14…...
前端ul-image的src接收base64快捷写法
前端ul-image的src接收base64快捷写法 data:image/png;base64,你的base64数据 注意如果是jpg就改成jpg,中间的逗号格式要注意,/注意不要反了 假设后端返回的detail中的url已经是base64格式,下面是示例 <u-image height"120rpx"…...

交通违法拍照数据集,可识别接打电话,不系安全带的行为,支持YOLO,COCO JSON,VOC XML格式的标注数据集 最高正确识别率可达88.6%
交通违法拍照数据集 数据集概述 数据来源:交通监控摄像头、执法记录仪、公开数据集数据类型:图像、视频、元数据(时间、地点、车辆信息)违法类型标注:接打电话、未系安全带 数据采集与标注方法 采集设备࿱…...

Qt OpenGL 3D 编程入门
Qt 提供了强大的 OpenGL 集成功能,使得在 Qt 应用中实现 3D 图形变得更加简单。以下是使用 Qt 进行 OpenGL 3D 编程的基础知识。 1. 环境配置 创建 Qt 项目 新建 Qt Widgets Application 项目 在 .pro 文件中添加 OpenGL 模块: qmake QT co…...

性能优化 - 工具篇:基准测试 JMH
文章目录 Pre引言1. JMH 简介2. JMH 执行流程详解3. 关键注解详解3.1 Warmup3.2 Measurement3.3 BenchmarkMode3.4 OutputTimeUnit3.5 Fork3.6 Threads3.7 Group 与 GroupThreads3.8 State3.9 Setup 与 TearDown3.10 Param3.11 CompilerControl 4. 示例代码与分析4.1 关键点解读…...
Ubuntu 中安装 PostgreSQL 及常规操作指南
目录 一、安装 PostgreSQL 最新版本安装(推荐) 安装特定版本(如 14) 二、基本服务管理 三、连接数据库 四、常规数据库操作 1. 用户与权限管理 2. 数据库管理 3. 表操作 4. 数据操作 五、常用 psql 元命令 六、备份与恢…...

Nginx网站服务:从入门到LNMP架构实战
🏡作者主页:点击! Nginx-从零开始的服务器之旅专栏:点击! 🐧Linux高级管理防护和群集专栏:点击! ⏰️创作时间:2025年5月30日14点22分 前言 说起Web服务器,…...

Java面试八股--08-数据结构和算法篇
1、怎么理解时间复杂度和空间复杂度 时间复杂度和空间复杂度一般是针对算法而言,是衡量一个算法是否高效的重要标准。先纠正一个误区,时间复杂度并不是算法执行的时间,在纠正一个误区,算法不单单指冒泡排序之类的,一个…...

Java面试八股--06-Linux篇
目录 一、Git 1、工作中git开发使用流程(命令版本描述) 2.Reset与Rebase,Pull与Fetch的区别 3、git merge和git rebase的区别 4、git如何解决代码冲突 5、项目开发时git分支情况 二、Linux 1、Linux常用的命令 2、如何查看测试项目的…...
Ajax技术分析方法全解:从基础到企业级实践(2025最新版)
引言 Ajax技术自2005年正式命名以来,已支撑全球83%的Web应用实现异步交互。2025年最新数据显示,单页面应用(SPA)的Ajax请求密度已达日均120亿次/应用。本文将系统化解析Ajax分析方法论,涵盖从基础原理到企业级工程实践的完整技术栈。 一、Ajax技术架构解构 1.1 核心组件…...
Unity 性能优化终极指南 — GameObject 篇
🎯 Unity 性能优化终极指南 — GameObject 方法篇 🧩 GameObject 是什么?—— Unity世界的核心实体 GameObject 是 Unity 引擎中最核心、最基础的构建单元。它代表了场景中的一个实体对象,可以是一个角色、一个UI元素、一盏灯光、…...

dvwa7——SQL Injection
LOW: f12打开hackbar 一:判断注入类型 输入id1报错 闭合单引号 ,页面恢复正常 所以为单引号字符型 二:开始攻击 1.判断列数 ?id1 order by 2-- 到3的时候开始报错,所以一共两列 2.爆回显位置 ?id-1 union s…...
Spring AI 项目实战(四):Spring AI + DeepSeek 超参数优化——智能化机器学习平台(附完整源码)
系列文章 序号文章名称1Spring AI 项目实战(一):Spring AI 核心模块入门2Spring AI 项目实战(二):Spring AI + DeepSeek 深度实战(附完整源码)3Spring AI 项目实战(三):Spring AI + DeepSeek 打造智能客服系统(附完整源码)4Spring AI 项目实战(四):Spring AI +…...
Axure疑难杂症:中继器图片替换功能优化(支持修改已有记录-玩转中继器)
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢!如有帮助请订阅专栏! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 案例视频: 中继器图片替换功能优化 课程主题:中继器图片替换功能优化(支持修改已有记录) 主要内…...

sqlite3 命令行工具详细介绍
一、启动与退出 启动数据库连接 sqlite3 [database_file] # 打开/创建数据库文件(如 test.db) sqlite3 # 启动临时内存数据库 (:memory:) sqlite3 :memory: # 显式启动内存数据库文件不存在时自动创建不指定文件名则使用临时内…...

ubuntu 22.04 编译安装nignx 报错 openssl 问题
前言 Ubuntu 20.04 中安装 Nginx (通过传包编译的方式)、开启关闭防火墙、开放端口号 在ubuntu 22.04.3 服务器上照着上面的文章 通过传包编译的方式安装nginx-1.18.0 的时候报错,报错内容如下: src/event/ngx_event_openssl.c: In function ‘ngx_ssl…...

线程相关面试题
提示:线程相关面试题,持续更新中 文章目录 一、Java线程池1、Java线程池有哪些核心参数,分别有什么的作用?2、线程池有哪些拒绝策略?3、说一说线程池的执行流程?4、线程池核心线程数怎么设置呢?4、Java线程…...