Domain Model¶
The backend of Munchies is divided into microservices that follows the Domain-Driven Design, inside a Hexagonal layout. Each microservice has the same folder structure and naming regardless of technological stack.
This section describes that structure, its class hierarchy, and how the layers depend on one another, using
order-service (
implemented in Kotlin)
and payment-service (implemented in
Express).
Layers and dependency direction¶
As mentioned above, each microservice follows the Hexagonal layout, with it's infrastructure, application
and domain layers:
flowchart LR
subgraph infra["infrastructure"]
direction TB
controller["adapter/inbound/web/controller"]
mongo["adapter/outbound/mongo"]
kafka["adapter/outbound/kafka"]
client["adapter/outbound/<other-service>"]
end
subgraph app["application"]
direction TB
inbound["port/inbound (one interface per use case)"]
usecase["usecase (implements the inbound port)"]
end
subgraph dom["domain"]
direction TB
model["model (entities, value objects)"]
factory["factory (aggregate construction)"]
outbound["port (outbound interfaces)"]
end
controller -->|" calls "| inbound
inbound -.->|" implemented by "| usecase
usecase --> model
usecase --> factory
usecase -->|" depends on "| outbound
mongo -.->|" implements "| outbound
kafka -.->|" implements "| outbound
client -.->|" implements "| outbound
classDef domain fill: #9956e0, stroke: #333, stroke-width: 2px, color: #fff
classDef application fill: #4a7fd6, stroke: #333, stroke-width: 2px, color: #fff
classDef infrastructure fill: #e07b39, stroke: #333, stroke-width: 2px, color: #fff
Solid arrows are plain calls; dashed arrows represent a dependency from another class/interface.
Package structure¶
Every service follows the same folder layout under its module root (<service>/src/main/kotlin/... for Kotlin
services, <service>/src/main/ts for Express.js ones):
domain/
model/ entities and value objects
factory/ aggregate construction logic (only where non-trivial)
port/ outbound interfaces (repository, external clients, publishers)
application/
usecase/ one class per use case
port/inbound/ one inbound port interface per use case
port/inbound/command/ input Command objects (optional, for non-trivial inputs)
infrastructure/
adapter/inbound/web/controller/ REST controllers
adapter/inbound/web/config/ DI wiring, OpenAPI configuration
adapter/outbound/mongo/document/ Mongo document classes
adapter/outbound/mongo/repository/ repository implementation
adapter/outbound/mongo/factory/ domain <-> document mapping
adapter/outbound/kafka/ Kafka producers/consumers
adapter/outbound/<other-service>/ clients for other microservices' REST APIs
adapter/dto/factory/ DTO <-> Command mapping
Class hierarchy: the Order aggregate¶
Here is a high level representation of the domain model of the system.
classDiagram
class User {
+UserId id
+UserProfile profile
}
class UserProfile {
+String username
+Email email
+UserRole role
}
class UserCredentials {
+UserId id
+String passwordHash
}
class UserRole {
<<enumeration>>
CUSTOMER
MANAGER
}
class Restaurant {
+RestaurantId id
+UserId managerId
+RestaurantDetails details
}
class Menu {
+MenuId id
+RestaurantId restaurantId
+MenuName name
}
class Category {
+CategoryId id
+CategoryName name
}
class MenuItem {
+MenuItemId id
+MenuItemDetails details
+Money price
}
class Order {
<<abstract>>
+OrderId id
+RestaurantId restaurantId
+CustomerId customerId
+OrderStatus status
+List~OrderItem~ items
+Boolean payed
}
class DeliveryOrder {
+DeliveryInfo deliveryInfo
}
class TakeawayOrder {
+TakeawayInfo takeawayInfo
}
class DineInOrder {
+TableInfo tableInfo
}
class OrderItem {
+MenuItemId menuItemId
+Int quantity
}
class OrderStatus {
<<enumeration>>
PENDING
PREPARING
READY
ON_THE_WAY
COMPLETED
CANCELLED
}
class Payment {
+PaymentId id
+OrderId orderId
+PaymentStatus status
+Number amount
+Currency currency
+PaymentMethod method
}
User "1" *-- "1" UserProfile: has
User "1" *-- "0..1" UserCredentials: secures
UserProfile --> UserRole: has
Restaurant "1" *-- "*" Menu: offers
Menu "1" *-- "*" Category: groups
Category "1" *-- "*" MenuItem: contains
Order <|-- DeliveryOrder
Order <|-- TakeawayOrder
Order <|-- DineInOrder
Order "1" *-- "*" OrderItem: contains
Order --> OrderStatus: has
Restaurant ..> User: managerId
Order ..> Restaurant: restaurantId
Order ..> User: customerId
OrderItem ..> MenuItem: menuItemId
Payment ..> Order: orderId