Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 13 additions & 34 deletions .agents/tools/architecture/clean-ddd-hexagonal-skill/hexagonal.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,19 @@

```mermaid
flowchart TB
subgraph DriverSide["DRIVER SIDE (Primary / Inbound / Left)"]
REST["REST API Adapter"]
CLI["CLI Adapter"]
DriverPorts["DRIVER PORTS\n(Use Case Interfaces)"]
REST --> DriverPorts
CLI --> DriverPorts
subgraph DriverSide["DRIVER SIDE (Primary / Inbound)"]
REST["REST API"] --> DriverPorts["DRIVER PORTS\n(Use Case Interfaces)"]
CLI["CLI"] --> DriverPorts
end

subgraph Hexagon["THE HEXAGON"]
subgraph AppCore["APPLICATION CORE"]
subgraph Domain["DOMAIN\n(Business Logic)"]
end
end
Domain["DOMAIN\n(Business Logic)"]
end

subgraph DrivenSide["DRIVEN SIDE (Secondary / Outbound / Right)"]
DrivenPorts["DRIVEN PORTS\n(Repository Interfaces)"]
Postgres["Postgres Adapter"]
RabbitMQ["RabbitMQ Adapter"]
DrivenPorts --> Postgres
DrivenPorts --> RabbitMQ
subgraph DrivenSide["DRIVEN SIDE (Secondary / Outbound)"]
DrivenPorts["DRIVEN PORTS\n(Repository Interfaces)"] --> Postgres["Postgres"]
DrivenPorts --> RabbitMQ["RabbitMQ"]
end

DriverPorts --> AppCore
AppCore --> DrivenPorts

DriverPorts --> Domain
Domain --> DrivenPorts
Comment on lines 10 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The simplification of the Mermaid diagram, specifically removing the AppCore subgraph and connecting DriverPorts directly to Domain, might oversimplify the architecture and misrepresent the dependency flow. In Hexagonal Architecture, driver adapters interact with application services (use cases), which then orchestrate the domain logic. The AppCore layer represented this crucial application service layer. The direct connection DriverPorts --> Domain implies that adapters are calling the domain logic directly, which is generally discouraged. While Rule 1 suggests conciseness in core files, Rule 4 and Rule 5 emphasize restoring key concepts and details for clarity and technical accuracy.

References
  1. Maintain a tiered documentation structure with a concise core file and detailed reference files.
  2. Restore detailed explanations for key concepts for clarity.
  3. When documenting restrictions, provide the technical reason behind them.

style DriverSide fill:#3b82f6,stroke:#2563eb,color:white
style Hexagon fill:#10b981,stroke:#059669,color:white
style DrivenSide fill:#f59e0b,stroke:#d97706,color:white
Expand All @@ -59,14 +46,12 @@ export interface IOrderRepositoryPort {
save(order: Order): Promise<void>;
delete(order: Order): Promise<void>;
}
// application/ports/driven/event_publisher_port.ts
export interface IEventPublisherPort {
publish(event: DomainEvent): Promise<void>;
publishAll(events: DomainEvent[]): Promise<void>;
}
// application/ports/driven/payment_gateway_port.ts
export interface IPaymentGatewayPort {
charge(amount: Money, paymentMethod: PaymentMethod): Promise<PaymentResult>;
charge(amount: Money, method: PaymentMethod): Promise<PaymentResult>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parameter paymentMethod was more descriptive than method. While method is shorter, paymentMethod provides better clarity about the purpose of the parameter, improving code readability. It's generally better to favor descriptive names over shorter ones.

Suggested change
charge(amount: Money, method: PaymentMethod): Promise<PaymentResult>;
charge(amount: Money, paymentMethod: PaymentMethod): Promise<PaymentResult>;

refund(paymentId: PaymentId, amount: Money): Promise<RefundResult>;
}
```
Expand All @@ -78,17 +63,11 @@ export interface IPaymentGatewayPort {
```typescript
// infrastructure/adapters/driver/rest/order_controller.ts
export class OrderController {
constructor(
private readonly placeOrder: IPlaceOrderPort,
private readonly getOrder: IGetOrderPort,
) {}

constructor(private readonly placeOrder: IPlaceOrderPort, private readonly getOrder: IGetOrderPort) {}
async create(req: Request, res: Response): Promise<void> {
const orderId = await this.placeOrder.execute({
customerId: req.user.id,
items: req.body.items.map((item: any) => ({
productId: item.product_id, quantity: item.quantity,
})),
items: req.body.items.map((i: any) => ({ productId: i.product_id, quantity: i.quantity })),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using i as a parameter name in the .map() callback is less descriptive than the original item. While shorter, item more clearly communicates what the variable represents. Additionally, formatting the mapped object across multiple lines improves readability. I'd recommend restoring the original, more descriptive variable name and multi-line formatting.

Suggested change
items: req.body.items.map((i: any) => ({ productId: i.product_id, quantity: i.quantity })),
items: req.body.items.map((item: any) => ({
productId: item.product_id, quantity: item.quantity,
})),

});
res.status(201).json({ id: orderId.value });
}
Expand All @@ -112,7 +91,7 @@ class InMemoryOrderRepository implements IOrderRepositoryPort: # for tests
delete(order): orders.delete(order.id.value)

class StripePaymentGateway implements IPaymentGatewayPort:
charge(amount, paymentMethod) -> PaymentResult:
charge(amount, method) -> PaymentResult:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the TypeScript interface change, renaming paymentMethod to method reduces clarity. The name paymentMethod is more descriptive and improves the readability of the function signature. It's generally better to favor descriptive names over shorter ones.

Suggested change
charge(amount, method) -> PaymentResult:
charge(amount, paymentMethod) -> PaymentResult:

intent = stripe.paymentIntents.create({amount: amount.cents, ...})
return PaymentResult.success(PaymentId.from(intent.id))
refund(paymentId, amount): stripe.refunds.create({paymentIntent: paymentId.value, ...})
Expand Down
Loading