Canteen Module — C3/C4 Architecture¶
Auto-generated by c4docgen on 2026-04-27. Do not edit manually — regenerate with
./gradlew generateC4Docs.
Component and code-level architecture for the Canteen module. For system-wide context (C1 — System Context, C2 — Container), see docs/c4-architecture.md.
C3 — Component¶
Internal components of the Canteen module, following the flat package structure established by the sample module.
C4Component
title Canteen Module — Component Diagram
Container_Boundary(canteen, "Canteen Module") {
Component(controllers, "Controllers", "@RestController", "REST endpoints for CanteenSetting, CanteenStudentsDirectory, CanteenUser, CateringService, Dashboard, FeedingList, ItemCategory, Item, ItemVariant, MealTicket, Menu, MenuSchedule, MenuSection, Order, ParentCateringService, ParentMenu, ParentOrder, Report")
Component(services, "Services", "@Service", "Business logic, caching, event publishing")
Component(repositories, "Repositories", "JpaRepository", "Data access interfaces")
Component(mappers, "Mappers", "@Component", "Entity ↔ DTO conversion")
Component(producers, "Event Producers", "@Component", "Publishes domain events to Kafka")
Component(listeners, "Event Listeners", "@KafkaListener", "Consumes domain events from Kafka")
Component(entities, "Entities", "@Entity", "JPA entity classes")
ComponentDb(dtos, "DTOs", "Java Records", "Request/response data transfer objects")
Component(constants, "Constants", "static final", "API paths, Kafka topics, cache names")
}
Rel(controllers, services, "Delegates to")
Rel(controllers, mappers, "Converts via")
Rel(services, repositories, "Persists via")
Rel(services, mappers, "Converts via")
Rel(services, producers, "Emits events via")
Rel(listeners, services, "May trigger")
Rel(repositories, entities, "Manages")
Component → Package mapping¶
| Component | Package | Key classes |
|---|---|---|
| Controllers | controllers/ |
CanteenSettingController, CanteenStudentsDirectoryController, CanteenUserController, CateringServiceController, DashboardController, FeedingListController, ItemCategoryController, ItemController, ItemVariantController, MealTicketController, MenuController, MenuScheduleController, MenuSectionController, OrderController, ParentCateringServiceController, ParentMenuController, ParentOrderController, ReportController |
| Request DTOs | controllers/dto/request/ |
CreateCanteenSettingRequest, UpdateCanteenSettingRequest, CreateCanteenStudentsDirectoryRequest, UpdateCanteenStudentsDirectoryRequest, CreateCanteenUserRequest, UpdateCanteenUserRequest, CreateCateringServiceRequest, UpdateCateringServiceRequest, CreateDashboardRequest, UpdateDashboardRequest, CreateFeedingListRequest, UpdateFeedingListRequest, CreateItemCategoryRequest, UpdateItemCategoryRequest, CreateItemRequest, UpdateItemRequest, CreateItemVariantRequest, UpdateItemVariantRequest, CreateMealTicketRequest, UpdateMealTicketRequest, CreateMenuRequest, UpdateMenuRequest, CreateMenuScheduleRequest, UpdateMenuScheduleRequest, CreateMenuSectionRequest, UpdateMenuSectionRequest, CreateOrderRequest, UpdateOrderRequest, CreateParentCateringServiceRequest, UpdateParentCateringServiceRequest, CreateParentMenuRequest, UpdateParentMenuRequest, CreateParentOrderRequest, UpdateParentOrderRequest, CreateReportRequest, UpdateReportRequest, etc. |
| Response DTOs | controllers/dto/response/ |
CanteenSettingResponse, CanteenStudentsDirectoryResponse, CanteenUserResponse, CateringServiceResponse, DashboardResponse, FeedingListResponse, ItemCategoryResponse, ItemResponse, ItemVariantResponse, MealTicketResponse, MenuResponse, MenuScheduleResponse, MenuSectionResponse, OrderResponse, ParentCateringServiceResponse, ParentMenuResponse, ParentOrderResponse, ReportResponse, etc. |
| Services | services/ |
CanteenSettingService, CanteenStudentsDirectoryService, CanteenUserService, CateringServiceService, DashboardService, ExcelExportService, FeedingListService, ItemCategoryService, ItemService, ItemVariantService, MealTicketService, MenuScheduleService, MenuSectionService, MenuService, OrderService, ParentMenuService, ParentOrderService, ReportService |
| Repositories | repositories/ |
CanteenOrderRepository, CanteenSettingRepository, CanteenUserRepository, CateringServiceRepository, ItemCategoryRepository, ItemRepository, ItemVariantRepository, MealTicketRepository, MenuAudienceRepository, MenuRepository, MenuScheduleRepository, MenuSectionRepository, OrderItemRepository, OrderStatusHistoryRepository, TicketPriceRepository |
| Entities | entities/ |
CanteenOrder, CanteenSetting, CanteenUser, CateringService, Item, ItemCategory, ItemVariant, MealTicket, Menu, MenuAudience, MenuSchedule, MenuSection, OrderItem, OrderStatusHistory, TicketPrice |
| Mappers | mappers/ |
CanteenSettingMapper, CanteenStudentsDirectoryMapper, CanteenUserMapper, CateringServiceMapper, FeedingListMapper, ItemCategoryMapper, ItemMapper, ItemVariantMapper, MealTicketMapper, MenuMapper, MenuScheduleMapper, MenuSectionMapper, OrderMapper |
| Event Producers | events/producers/ |
ItemEventProducer, MealTicketEventProducer, MenuEventProducer, OrderEventProducer |
| Event Listeners | events/listeners/ |
FeedingListEventListener, InvoicePaidEventListener, MenuEventListener, OrderEventListener |
| Constants | constants/ |
Constants, KafkaTopics, CacheNames |
C3.1 — Entity Relationship Model¶
The 15 core entities and their relationships.
erDiagram
MealTicket ||--o{ CanteenOrder : "has many"
Menu ||--o{ CanteenOrder : "has many"
CateringService }o--o{ CanteenUser : "linked via"
CateringService ||--o{ Item : "has many"
ItemCategory ||--o{ Item : "has many"
Item ||--o{ ItemVariant : "has many"
Menu ||--o{ MealTicket : "has many"
Menu }o--o{ MenuAudience : "linked via"
MenuSection ||--o{ MenuSchedule : "has many"
Item ||--o{ MenuSchedule : "has many"
Menu ||--o{ MenuSection : "has many"
Item ||--o{ OrderItem : "has many"
MenuSection ||--o{ OrderItem : "has many"
MealTicket }o--o{ TicketPrice : "linked via"
CanteenOrder {
UUID id PK
String orderNumber
UUID studentId FK
UUID mealTicketId FK
OrderType orderType "TICKET, PRE_ORDER, POS"
BigDecimal totalPrice
PaymentMode paymentMode "SIKA_ID, MOBILE_MONEY, CARD, CREDIT, CASH, ALLOCATION"
OrderStatus status "DRAFT, PENDING_PAYMENT, PAID, SERVED, PARTIALLY_SERVED, CANCELLED"
LocalDateTime scheduledDate
UUID batchId FK
UUID menuId FK
UUID createdBy
Instant redeemedAt
Instant cancelledAt
UUID cancelledBy
UUID schoolId FK
String invoiceId
BigDecimal serviceFee
Instant paidAt
Instant createdAt
Instant updatedAt
}
CanteenSetting {
UUID id PK
RedemptionMode redemptionMode "AUTO_REDEEM, AUTO_REDEEM_PRESENT, SCAN_TO_REDEEM"
UUID schoolId FK
Instant createdAt
Instant updatedAt
}
CanteenUser {
UUID id PK
UUID userId FK
UUID cateringServiceId FK
CanteenRole role "CANTEEN_ADMIN, CANTEEN_MANAGER, POS_OPERATOR"
boolean active
UUID schoolId FK
Instant createdAt
Instant updatedAt
}
CateringService {
UUID id PK
String name
String logo
String email
String phone
UUID schoolId FK
SettlementAccountType settlementAccountType "BANK, MOBILE_MONEY"
String settlementAccountName
String settlementAccountNumber
GhanaBank settlementBank "ABSA_BANK_GHANA, ACCESS_BANK_GHANA, AGRICULTURAL_DEVELOPMENT_BANK, BANK_OF_AFRICA_GHANA, CALBANK, CONSOLIDATED_BANK_GHANA, ECOBANK_GHANA, FBN_BANK_GHANA, FIDELITY_BANK_GHANA, FIRST_ATLANTIC_BANK, FIRST_NATIONAL_BANK_GHANA, GCB_BANK, GUARANTY_TRUST_BANK_GHANA, NATIONAL_INVESTMENT_BANK, OMNIBSIC_BANK_GHANA, PRUDENTIAL_BANK, REPUBLIC_BANK_GHANA, SOCIETE_GENERALE_GHANA, STANBIC_BANK_GHANA, STANDARD_CHARTERED_BANK_GHANA, UNITED_BANK_FOR_AFRICA_GHANA, UNIVERSAL_MERCHANT_BANK, ZENITH_BANK_GHANA"
MobileMoneyProvider settlementMobileMoneyProvider "MTN, TELECEL, AIRTELTIGO"
BigDecimal platformServiceFeeOverride
Instant createdAt
Instant updatedAt
}
Item {
UUID id PK
UUID cateringServiceId FK
UUID itemCategoryId FK
String name
String image
String description
String allergens
int maxQuantityPerDay
PricingStyle pricingStyle "FIXED, VARIANT, NO_PRICE"
BigDecimal defaultPrice
String currency
UUID schoolId FK
UUID createdBy
Instant createdAt
Instant updatedAt
}
ItemCategory {
UUID id PK
String name
String description
String themeColor
UUID schoolId FK
Instant createdAt
Instant updatedAt
}
ItemVariant {
UUID id PK
UUID itemId FK
String variantName
BigDecimal price
AudienceScope audience "ALL, SPECIFIC"
List targetCampusIds
List targetClassroomIds
Instant createdAt
Instant updatedAt
}
MealTicket {
UUID id PK
UUID menuId FK
String name
UUID academicTermId FK
PricingMethod pricingMethod "DAILY, MONTHLY, TERMLY"
PricingScheme pricingScheme "UNIFORM, DIFFERENTIAL"
BigDecimal uniformPrice
boolean active
UUID schoolId FK
Instant createdAt
Instant updatedAt
}
Menu {
UUID id PK
String name
String description
MenuPurpose purpose "INFO_ONLY, PARENTS_CAN_ORDER"
PaymentType paymentType "ORDER_AND_PAY_ITEM_PRICING, ORDER_ONLY_NO_PAYMENT, ORDER_AND_PAY_TICKET_PRICING"
LocalDate startDate
LocalDate endDate
DeadlineType orderingDeadlineType "DAILY, WEEKLY"
LocalTime dailyCutoffTime
int dailyCutoffDays
DayOfWeek weeklyCutoffDay "MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY"
LocalTime weeklyCutoffTime
int orderCancellationHours
boolean published
AudienceScope audience "ALL, SPECIFIC"
List targetCampusIds
List targetSectionIds
List targetClassroomIds
List excludedStudentIds
int visibilityDays
UUID schoolId FK
UUID updatedBy
String updatedByName
Instant createdAt
Instant updatedAt
}
MenuAudience {
UUID id PK
UUID menuId FK
UUID campusId FK
UUID classroomId FK
Instant createdAt
Instant updatedAt
}
MenuSchedule {
UUID id PK
UUID menuSectionId FK
UUID itemId FK
LocalDate date
UUID scheduledViaCategoryId FK
int stockAvailable
SchedulePattern recurrenceRule "SINGLE_DAY, EVERY_DAY, EVERY_WEEKDAY, SPECIFIC_DAYS"
Instant createdAt
Instant updatedAt
}
MenuSection {
UUID id PK
UUID menuId FK
String name
LocalTime servingStartTime
LocalTime servingEndTime
SelectionMode selectionMode "SINGLE, MULTIPLE"
String themeColor
Instant createdAt
Instant updatedAt
}
OrderItem {
UUID id PK
UUID orderId FK
UUID itemId FK
UUID variantId FK
UUID menuSectionId FK
int quantity
BigDecimal unitPrice
boolean served
Instant servedAt
long servedBy
String servedByName
Instant createdAt
Instant updatedAt
}
OrderStatusHistory {
UUID id PK
UUID orderId FK
OrderStatus fromStatus "DRAFT, PENDING_PAYMENT, PAID, SERVED, PARTIALLY_SERVED, CANCELLED"
OrderStatus toStatus "DRAFT, PENDING_PAYMENT, PAID, SERVED, PARTIALLY_SERVED, CANCELLED"
UUID changedBy
Instant changedAt
}
TicketPrice {
UUID id PK
UUID mealTicketId FK
UUID targetGroupId FK
BigDecimal price
Instant createdAt
Instant updatedAt
}
Cross-module references¶
| Field | Source module | Purpose |
|---|---|---|
CanteenOrder.studentId |
School | Links to Student |
CanteenOrder.schoolId |
School | Links to School |
CanteenSetting.schoolId |
School | Links to School |
CanteenUser.schoolId |
School | Links to School |
CateringService.schoolId |
School | Links to School |
Item.schoolId |
School | Links to School |
ItemCategory.schoolId |
School | Links to School |
MealTicket.schoolId |
School | Links to School |
Menu.schoolId |
School | Links to School |
MenuAudience.campusId |
School | Links to Campus |
MenuAudience.classroomId |
School | Links to Classroom |
C3.2 — REST API Structure¶
All endpoints follow the pattern established by the sample module.
| Entity | Base path | Operations |
|---|---|---|
| CanteenSetting | /api/canteen/settings |
GET (list, paginated), PUT /{id} |
| CanteenStudentsDirectory | /api/canteen/students |
GET (list, paginated) |
| CanteenUser | /api/canteen/users |
GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id} |
| CateringService | /api/canteen/catering-services |
GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id} |
| Dashboard | /api/canteen/dashboard |
GET (list, paginated), GET /export |
| FeedingList | /api/canteen/feeding-list |
GET (list, paginated), PUT /bulk-redeem, GET /export |
| ItemCategory | /api/canteen/item-categories |
GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id} |
| Item | /api/canteen/items |
GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}, GET /library, GET /recently-used |
| ItemVariant | /api/canteen/items/{itemId}/variants |
GET (list, paginated), POST, DELETE /{variantId}, PUT /{variantId} |
| MealTicket | /api/canteen/meal-tickets |
GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id} |
| Menu | /api/canteen/menus |
GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}, PUT /{id}/publish, GET /{id}/schedule, PUT /{id}/unpublish |
| MenuSchedule | /api/canteen/menus/{menuId}/sections/{sectionId}/schedules |
GET (list, paginated), POST, DELETE `, PATCH/bulk-stock, DELETE/{scheduleId}, PATCH/{scheduleId}` |
| MenuSection | /api/canteen/menus/{menuId}/sections |
GET (list, paginated), POST, DELETE /{sectionId}, PUT /{sectionId} |
| Order | /api/canteen/orders |
GET (list, paginated), GET /{id}, POST, PUT /{id}/cancel, PUT /{id}/items/{orderItemId}/serve, PUT /{id}/redeem |
| ParentCateringService | `` | GET /api/canteen/parent/catering-services, GET /api/canteen/parent/catering-services/{cateringServiceId}/schedule |
| ParentMenu | `` | GET /api/canteen/parent/children/{studentId}/menus, GET /api/canteen/parent/children/{studentId}/menus/{menuId}/schedule, GET /api/canteen/parent/kids-schedule |
| ParentOrder | `` | GET /api/canteen/parent/orders, POST /api/canteen/parent/orders, GET /api/canteen/parent/orders/batch/{batchId}, PUT /api/canteen/parent/orders/batch/{batchId}/checkout, DELETE /api/canteen/parent/orders/{orderId}, GET /api/canteen/parent/orders/{orderId}, PUT /api/canteen/parent/orders/{orderId}, PUT /api/canteen/parent/orders/{orderId}/cancel |
| Report | /api/canteen/reports |
GET /orders, GET /orders/export, GET /sales, GET /sales/export, GET /sika-id, GET /sika-id/export, GET /tickets, GET /tickets/export |
All list endpoints support page (default: 0) and size (default: 20) query parameters, returning PagedResponse<T> from modules/shared/.
C3.3 — Event Flow¶
Domain events are published to Kafka on key state changes.
flowchart LR
Service -->|publish| Producer
Producer -->|send| Kafka["Kafka Topic<br/>canteen.*"]
Kafka -->|consume| Listener
subgraph Topics
T1["canteen.orders"]
T2["canteen.menus"]
T3["canteen.meal-tickets"]
T4["canteen.items"]
T5["canteen.feeding-list"]
end
Event payload structure (Java record):
*Event {
type: EventType (CREATED, UPDATED, DELETED)
id: UUID
... entity-specific fields ...
timestamp: Instant
}
C3.4 — Caching Strategy¶
Redis caching at the service layer, following the sample module pattern.
| Cache name | Key | Populated by | Evicted by |
|---|---|---|---|
menuSectionById |
entity UUID | findById, update, create |
delete |
cateringServiceById |
entity UUID | findById, update, create |
delete |
mealTicketById |
entity UUID | findById, update, create |
delete |
itemCategoryById |
entity UUID | findById, update, create |
delete |
itemById |
entity UUID | findById, update, create |
delete |
canteenSettingById |
entity UUID | update |
`` |
menuById |
entity UUID | unpublish, findById, update, create, publish |
delete |
Annotations: @Cacheable (read), @CachePut (write), @CacheEvict (delete).
C4 — Code Patterns¶
Implementation follows the conventions from the sample module.
Request flow¶
HTTP Request
→ Controller (validates, delegates)
→ Mapper.toEntity(request)
→ Service (business logic)
→ Repository.save(entity)
→ Mapper.toEvent(entity, CREATED)
→ EventProducer.publish(event)
→ Redis cache updated
→ Mapper.toResponse(entity)
→ HTTP Response (JSON)
Shared utilities (reuse from modules/shared/)¶
| Class | Usage |
|---|---|
PagedResponse<T> |
Wrap all paginated list responses |
ResourceNotFoundException |
Throw on entity-not-found (auto-mapped to 404 ProblemDetail) |
Conventions¶
- All DTOs are immutable Java records
- Constructor injection only (no
@Autowiredfields) - OpenAPI annotations on all controllers and DTOs (
@Tag,@Operation,@Schema) - Constants for API paths, Kafka topics, and cache names (no magic strings)
- Integration tests with Testcontainers (real Postgres, Kafka, Redis)
Related documents¶
| Document | Path |
|---|---|
| Sample module guide | Sample Guide |
| Root PRD index | docs/PRD.md |
| Backend system README | backend/README.md |