观点很多人认为整洁架构只适合大型项目而六边形架构更适用于中小型项目。我也用AI查过这个问题貌似有些模型却实是给出了这样的选型答案。这个观点听起来很合理我也在一些热门的架构书籍中看过这个观点甚至被广泛接受。但它经得起推敲吗质疑一大小如何界定大项目和小项目的划分没有客观标准。这个批评是成立的但是却又是【无理取闹】的。如果非要用一个指标来衡量复杂度那么port 的数量接口是一个最具体也最能反映问题的。我在实践中发现一但 port 增多六边形架构就越容易出现混乱很容易出现【找不到Adapter】或者【找错Adapter】的情况。为了让更多的朋友看懂本文选用 python 为代码示例质疑二六边形架构的双向问题六边形架构的 port 分为两类# 六边形架构的依赖方向 # 入站方向 入站Adapter → 入站Port → Domain # 出站方向 Domain ← 出站Port ← 出站Adapter其实从认知上来说在 Java , C# 这些优秀的面向对象语言之中 六边形架构就是一种鸡肋用分层架构就足够了。因为 Port 就是接口Adapter 就是实现那以 Java 的写法 接口有时可能从文件名看不出但实现一定是 XXXImpl.java, 而C# 就是返过来如IRepository-OrderRepository不过习惯了就好这是惯例的知识所以在这两种语言中去实践六边形会非常的便扭代码反而很难看。六边形是两个方向的分离那就意味着认知负担翻倍。在 Python 这种没有原生接口的语言中问题就显得更严重# ports/inbound/order_service.py from abc import ABC, abstractmethod class OrderServicePort(ABC): # 这就相当于 OrderService 的接口定义 abstractmethod def create_order(self, order_dto: OrderDTO) - Order: pass # ports/outbound/payment_gateway.py class PaymentGatewayPort(ABC): abstractmethod def charge(self, amount: Decimal) - PaymentResult: pass # 问题来了当你看到 Domain 层调用 payment_gateway 时 # 你需要去哪里找 Adapter # adapters/outbound/stripe/adapters/outbound/paypal/ # 还是 adapters/inbound/ 里有个奇怪的实现找不到 Adapter的情况在六边形架构中并不罕见尤其是项目演进一段时间后。最蛋疼的是你想用IDE去跳转实现代码那你得失望了因为这是一种抽象层级的定义与实现IDE并【认不出来】你得手工找。当 Adapter 很多的时候每次你都得在 In / Out 两个目录中来回横跳非常的麻烦接口越多你越接近地狱。质疑三整洁架构的单一方向整洁架构的依赖规则极其简单Frameworks → Interface Adapters → Use Cases → Entities ←———————————— 所有依赖指向内层 ————————————←# 整洁架构的结构 # entities/order.py dataclass class Order: id: OrderId items: list[OrderItem] status: OrderStatus def total_amount(self) - Decimal: return sum(item.price for item in self.items) # usecases/create_order.py class CreateOrderUseCase: def __init__(self, order_repo: OrderRepository, payment_gateway: PaymentGateway): self.order_repo order_repo self.payment_gateway payment_gateway def execute(self, command: CreateOrderCommand) - Order: order Order.create(command.items) self.payment_gateway.charge(order.total_amount()) self.order_repo.save(order) return order # adapters/payment/stripe_gateway.py class StripePaymentGateway(PaymentGateway): def charge(self, amount: Decimal) - PaymentResult: # Stripe SDK 调用 pass # adapters/persistence/sql_order_repo.py class SqlOrderRepository(OrderRepository): def save(self, order: Order) - None: # SQLAlchemy 持久化 pass依赖方向永远指向内核。无论项目大小这个规则不变。这就意味着整洁架构永远不会出现【循环依赖】有且只有具备单一方向依赖的架构是不会出现【循环依赖】的。对于 AI 辅助编程这种确定性极其重要你的AI是不会写错代码的或者写代码出错的机率大大降低我们尝试推理一下AI的推理方式Prompt: 在整洁架构中我需要添加一个新的支付方式 PayPal AI 的推理路径 1. PaymentGateway 接口在 usecases 层定义 ✓ 2. 新的 PayPalGateway 在 adapters 层实现 ✓ 3. 依赖方向adapters → usecases ✓ 4. 不需要修改任何内层代码 ✓对比六边形架构Prompt: 在六边形架构中我需要添加一个新的支付方式 PayPal AI 的推理路径 1. 这是入站 Port 还是出站 Port需要判断... 2. 如果是出站 PortAdapter 放在哪个目录 3. 需要检查现有的 Port 定义位置... 4. 命名约定是什么PaymentGatewayPortPayPalAdapter反驳与修正我遇到最有意思的一些常见问题反驳一CRUD 场景呢有人会说整洁架构对 CRUD 项目来说太重了。这个批评是伪需求。CRUD 场景下讨论架构是【没有意义的】。首先CURD不是业务操作是数据操作没有业务性。没有业务复杂度就没有架构问题只有代码规范问题。把整洁架构对 CRUD 冗余当作批评点本身就是靶子立错了。# CRUD 场景直接在 Controller 里操作数据库完全没问题 app.route(/users, methods[POST]) def create_user(): user User(namerequest.json[name]) db.session.add(user) db.session.commit() return jsonify(user.to_dict())这不是糟糕的架构这是没有架构。对于纯 CRUD这就是正确的选择。即使这在这个create_user中你直接写SQL最多只能是批评代码的人太水了将SQL侵入代码这种写法都干得出来充其量是代码太屎而是不架构有问题。反驳二穿层问题又有人说整洁架构的层次多了会有穿层的诱惑。 另一种说法叫【数据透传】穿层不是诱惑是违规。# 错误示例Frameworks 层直接调用 Entities 层 app.route(/orders, methods[POST]) def create_order(): order Order.create(request.json) # 跳过了 Use Cases 层 order_repo.save(order) # 跳过了 Interface Adapters 层 return jsonify(order.to_dict())整洁架构的依赖规则是硬约束违反就是错的没有灰色地带。这和六边形架构的双向依赖有本质区别——前者是明确的错误后者是设计本身的复杂性。其实上面这个示例还漏了一点那就是DI如果你的整洁架构没有DI就容易出现上面的种情况因为你得去构造整洁架构中的构造只有在起动的装配位置出现通过DI管理实例用接口承接实现。这自然是不会出现所谓的 【透传】这么可笑的情况。第三回合核心洞察六边形架构 vs 整洁架构不是对比是进化把六边形架构和整洁架构放在一起对比选型本身就是认知错误与【伪命题】。从历史演进看架构年份核心贡献分层架构早期分离关注点六边形架构2005提出 Port/Adapter解耦内外部整洁架构2012统一依赖方向消除双向复杂性【整洁架构是六边形架构的进化版本】它修复了六边形的双向依赖复杂性。就像没人会问应该用 iPhone 15 还是 iPhone 4一样所以说问用六边形还是整洁架构也是伪命题。代码对比同一个业务场景# 六边形架构的目录结构 order-service/ ├── domain/ │ └── order.py ├── ports/ │ ├── inbound/ │ │ └── order_service.py # Port │ └── outbound/ │ └── payment_gateway.py # Port └── adapters/ ├── inbound/ │ └── rest/ │ └── order_controller.py # Adapter for order_service └── outbound/ ├── stripe/ │ └── stripe_payment.py # Adapter for payment_gateway └── paypal/ └── paypal_payment.py # Adapter for payment_gateway # 整洁架构的目录结构 order-service/ ├── entities/ │ └── order.py ├── usecases/ │ ├── create_order.py │ └── ports/ # 接口定义在这里 │ ├── order_repository.py │ └── payment_gateway.py └── adapters/ ├── controllers/ │ └── order_controller.py ├── gateways/ │ ├── stripe_gateway.py │ └── paypal_gateway.py └── persistence/ └── sql_order_repo.py关键区别六边形Port 分散在ports/inbound和ports/outbound需要记住两套规则整洁所有接口都在usecases/ports依赖方向统一指向entitiesPython 的特殊情况Python 没有原生接口只能用ABC抽象基类# 六边形架构中你需要区分这是入站 Port 还是出站 Port class OrderServicePort(ABC): # 入站 abstractmethod def create_order(self, dto: OrderDTO) - Order: ... class PaymentGatewayPort(ABC): # 出站 abstractmethod def charge(self, amount: Decimal) - PaymentResult: ... # 从代码本身你无法区分哪个是入站、哪个是出站 # 只能靠目录结构推断# 整洁架构中所有 Port 都在 usecases 层 # 它们的角色是统一的被外层实现 class OrderRepository(ABC): abstractmethod def save(self, order: Order) - None: ... class PaymentGateway(ABC): abstractmethod def charge(self, amount: Decimal) - PaymentResult: ... # 不需要区分方向只需要知道外层实现内层调用结论大小界定是伪命题用项目大小来选择架构没有客观标准用 port 数量来衡量复杂度反而更具体六边形的双向依赖是设计缺陷入站/出站两个方向增加了认知负担尤其在动态语言中整洁架构是进化不是替代它统一了依赖方向消除了六边形的复杂性独绝了循环依赖的发生对比本身就是错误的就像比较 iPhone 4 和 iPhone 15它们不是选择关系而是进化关系对 AI 编程极其友好单一依赖方向意味着 AI 可以更准确地推理代码结构附录依赖方向的可视化下面这个图是用AI画的六边形架构 ┌─────────────────┐ │ Inbound │ │ Adapter │ └────────┬────────┘ │ ▼ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │ Outbound │◄─────│ Domain │─────►│ Inbound │ │ Adapter │ │ │ │ Port │ └──────────────┘ └─────────────┘ └──────────────┘ ▲ │ │ │ └────────────┬───────┘ │ ┌──────▼──────┐ │ Outbound │ │ Port │ └─────────────┘ 依赖方向双向入站向内出站向外 整洁架构 ┌────────────────────────────────────────────────────────┐ │ Frameworks │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Interface Adapters │ │ │ │ ┌────────────────────────────────────────────┐ │ │ │ │ │ Use Cases │ │ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ │ │ Entities │ │ │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ │ │ └────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────────┘ 依赖方向单向永远指向内层