六边形架构下创建Lead:外部用户服务调用位置的选型咨询
Great question—let’s break this down through the lens of hexagonal architecture principles, since that’s your core context here. Your intuition about keeping the application service decoupled from API-specific inputs is spot-on, but let’s unpack both options to be clear on tradeoffs and best practices.
分析方案A:控制器层处理邮箱转User ID
This approach aligns perfectly with hexagonal architecture’s core goal: separating core business logic from external concerns (like API inputs or external service integrations). Here’s why it works:
- 应用层保持纯净: Your application service only receives the data it needs to fulfill its core responsibility—creating a Lead: a valid
user_idand Lead details. It doesn’t need to know anything about "email-to-user-id" translation, which is an API/adapter-layer concern, not a business rule. - 解耦外部服务依赖: The controller (an input adapter) handles the external service call to resolve the email to a
user_id. If the external service’s API changes (e.g., requires an additional parameter) or you switch to a different user lookup service later, only the controller/adapter needs to change—your core application service stays untouched.
关于是否需要再次验证User ID
This depends on your trust in the external service and your business rules:
- If the external service is your system’s authoritative identity provider (e.g., an internal auth service that validates the email exists and returns a valid, active user ID), you don’t need to re-verify in the application service. The controller has already done the work of translating the input to a trusted identifier.
- If your business requires confirming the user is still active/eligible at the moment of Lead creation, don’t hardcode another external service call in the application service. Instead, define a UserValidationPort (an abstract interface in the core layer) and have an adapter implement it to call the external service. This way, the application service depends on an abstraction, not a concrete external service.
分析方案B:应用服务调用外部服务
This approach introduces unnecessary coupling that hexagonal architecture is designed to avoid:
- 应用层耦合API输入: Your application service now has to handle the API-specific input (email) and know about the external service’s existence. This mixes adapter-layer concerns with core business logic.
- 维护成本上升: If you later change the API to accept a phone number instead of email, or switch to a different user lookup service, you’ll have to modify the application service—violating the open/closed principle.
That said, if "resolving a user via email to create a Lead" is a core business rule (e.g., your Lead creation logic explicitly requires verifying the email maps to a real user), you could still keep this logic in the application service—but only by relying on an abstract port, not a direct external service call. For example, define a LeadCreatorPort with a method like createLead(String userEmail, LeadDetails details), and have an adapter handle the email-to-user-id lookup. But this is an edge case; your original preference for keeping this in the controller is still cleaner.
结论
Your inclination toward Option A is absolutely correct for a hexagonal architecture implementation. It keeps your core application service focused on business logic, decoupled from API inputs and external service specifics. Just make sure:
- The controller’s external service call is wrapped in an adapter (not hardcoded), so you can easily swap or update the external service integration later.
- You clarify your business rules around user ID validation to avoid unnecessary duplicate calls.
内容的提问来源于stack exchange,提问作者Carlos

