Skip to content

School 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 School module. For system-wide context (C1 — System Context, C2 — Container), see docs/c4-architecture.md.


C3 — Component

Internal components of the School module, following the flat package structure established by the sample module.

C4Component
    title School Module — Component Diagram

    Container_Boundary(school, "School Module") {
        Component(controllers, "Controllers", "@RestController", "REST endpoints for AcademicHoliday, AcademicPeriod, AcademicYear, Campus, Classroom, Family, Guardian, MySchool, MyStaffProfile, ParentChildren, School, Section, Staff, Student")
        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/ AcademicHolidayController, AcademicPeriodController, AcademicYearController, CampusController, ClassroomController, FamilyController, GuardianController, MySchoolController, MyStaffProfileController, ParentChildrenController, SchoolController, SectionController, StaffController, StudentController
Request DTOs controllers/dto/request/ CreateAcademicHolidayRequest, UpdateAcademicHolidayRequest, CreateAcademicPeriodRequest, UpdateAcademicPeriodRequest, CreateAcademicYearRequest, UpdateAcademicYearRequest, CreateCampusRequest, UpdateCampusRequest, CreateClassroomRequest, UpdateClassroomRequest, CreateFamilyRequest, UpdateFamilyRequest, CreateGuardianRequest, UpdateGuardianRequest, CreateMySchoolRequest, UpdateMySchoolRequest, CreateMyStaffProfileRequest, UpdateMyStaffProfileRequest, CreateParentChildrenRequest, UpdateParentChildrenRequest, CreateSchoolRequest, UpdateSchoolRequest, CreateSectionRequest, UpdateSectionRequest, CreateStaffRequest, UpdateStaffRequest, CreateStudentRequest, UpdateStudentRequest, etc.
Response DTOs controllers/dto/response/ AcademicHolidayResponse, AcademicPeriodResponse, AcademicYearResponse, CampusResponse, ClassroomResponse, FamilyResponse, GuardianResponse, MySchoolResponse, MyStaffProfileResponse, ParentChildrenResponse, SchoolResponse, SectionResponse, StaffResponse, StudentResponse, etc.
Services services/ AcademicHolidayService, AcademicPeriodService, AcademicYearService, CampusService, ClassroomService, FamilyService, GuardianService, ParentChildrenService, SchoolLegacyImportService, SchoolService, SectionService, StaffService, StudentService
Repositories repositories/ AcademicHolidayRepository, AcademicPeriodRepository, AcademicYearRepository, CampusRepository, ClassroomRepository, FamilyRepository, GuardianRepository, SchoolRepository, SchoolStaffRepository, SectionRepository, StudentGuardianRepository, StudentRepository
Entities entities/ AcademicHoliday, AcademicPeriod, AcademicYear, Campus, Classroom, Family, Guardian, School, SchoolStaff, Section, Student, StudentGuardian
Mappers mappers/ AcademicHolidayMapper, AcademicPeriodMapper, AcademicYearMapper, CampusMapper, ClassroomMapper, FamilyMapper, GuardianMapper, SchoolMapper, SectionMapper, StaffMapper, StudentMapper
Event Producers events/producers/ AcademicHolidayEventProducer, AcademicPeriodEventProducer, AcademicYearEventProducer, CampusEventProducer, ClassroomEventProducer, FamilyEventProducer, GuardianEventProducer, SchoolEventProducer, SectionEventProducer, StaffEventProducer, StudentEventProducer
Event Listeners events/listeners/ AcademicHolidayEventListener, AcademicPeriodEventListener, AcademicYearEventListener, CampusEventListener, ClassroomEventListener, FamilyEventListener, GuardianEventListener, SchoolEventListener, SectionEventListener, StaffEventListener, StudentEventListener
Constants constants/ Constants, KafkaTopics, CacheNames

C3.1 — Entity Relationship Model

The 12 core entities and their relationships.

erDiagram
    AcademicYear ||--o{ AcademicHoliday : "has many"
    AcademicPeriod ||--o{ AcademicHoliday : "has many"
    AcademicYear ||--o{ AcademicPeriod : "has many"
    School ||--o{ AcademicYear : "has many"
    Campus ||--o{ AcademicYear : "has many"
    Section ||--o{ AcademicYear : "has many"
    School ||--o{ Campus : "has many"
    Section ||--o{ Classroom : "has many"
    School ||--o{ Family : "has many"
    Family ||--o{ Guardian : "has many"
    School ||--o{ SchoolStaff : "has many"
    Campus ||--o{ SchoolStaff : "has many"
    Campus ||--o{ Section : "has many"
    School ||--o{ Student : "has many"
    Classroom ||--o{ Student : "has many"
    Family ||--o{ Student : "has many"
    Student ||--o{ StudentGuardian : "has many"
    Guardian ||--o{ StudentGuardian : "has many"

    AcademicHoliday {
        UUID id PK
        UUID academicYearId FK
        UUID academicPeriodId FK
        String name
        LocalDate startDate
        LocalDate endDate
        String description
        Instant createdAt
        Instant updatedAt
        long legacyHolidayId
    }

    AcademicPeriod {
        UUID id PK
        UUID academicYearId FK
        String name
        LocalDate startDate
        LocalDate endDate
        Instant createdAt
        Instant updatedAt
        long legacyPeriodId
    }

    AcademicYear {
        UUID id PK
        UUID schoolId FK
        UUID campusId FK
        UUID sectionId FK
        String name
        LocalDate startDate
        LocalDate endDate
        AcademicPeriodType periodType "TERM, SEMESTER, QUARTER, TRIMESTER"
        boolean isActive
        Instant createdAt
        Instant updatedAt
        long legacyYearId
    }

    Campus {
        UUID id PK
        UUID schoolId FK
        String name
        String code
        String address
        String phone
        String email
        String principalName
        boolean isActive
        Instant createdAt
        Instant updatedAt
        long legacyCampusId
        Map legacyConfiguration
    }

    Classroom {
        UUID id PK
        UUID sectionId FK
        String name
        String code
        String academicYear
        int capacity
        UUID classTeacherId FK
        String roomNumber
        boolean isActive
        Instant createdAt
        Instant updatedAt
        long legacyGradeId
    }

    Family {
        UUID id PK
        UUID schoolId FK
        String familyName
        String familyCode
        String primaryAddress
        String primaryPhone
        String primaryEmail
        String notes
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    Guardian {
        UUID id PK
        UUID familyId FK
        GuardianType guardianType "FATHER, MOTHER, STEPFATHER, STEPMOTHER, GRANDFATHER, GRANDMOTHER, UNCLE, AUNT, OLDER_SIBLING, LEGAL_GUARDIAN, FOSTER_PARENT, RELATIVE, FAMILY_FRIEND, OTHER"
        String firstName
        String middleName
        String lastName
        String email
        String phonePrimary
        String phoneSecondary
        String occupation
        String employer
        String address
        String nationalId
        String profilePhotoUrl
        boolean isEmergencyContact
        boolean canPickupStudent
        boolean isPrimaryContact
        long legacyUserId
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    School {
        UUID id PK
        String name
        String code
        String logoUrl
        String address
        String phone
        String email
        String website
        String accreditationNumber
        String defaultCurrency
        long legacySchoolId
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    SchoolStaff {
        UUID id PK
        UUID schoolId FK
        UUID campusId FK
        String staffIdNumber
        String firstName
        String middleName
        String lastName
        String email
        String phone
        LocalDate dateOfBirth
        Gender gender "MALE, FEMALE, OTHER"
        String profilePhotoUrl
        StaffRole role "TEACHER, ADMINISTRATOR, HEADMASTER, SUPPORT, OTHER"
        String department
        EmploymentType employmentType "FULL_TIME, PART_TIME, CONTRACT"
        LocalDate hireDate
        StaffStatus status "ACTIVE, INACTIVE, ON_LEAVE, TERMINATED"
        long legacyUserId
        Instant createdAt
        Instant updatedAt
    }

    Section {
        UUID id PK
        UUID campusId FK
        String name
        String code
        int levelOrder
        String description
        boolean isActive
        Instant createdAt
        Instant updatedAt
    }

    Student {
        UUID id PK
        UUID schoolId FK
        UUID classroomId FK
        UUID familyId FK
        String studentIdNumber
        String firstName
        String middleName
        String lastName
        LocalDate dateOfBirth
        Gender gender "MALE, FEMALE, OTHER"
        String profilePhotoUrl
        String nationality
        String religion
        String bloodGroup
        String medicalNotes
        LocalDate enrollmentDate
        StudentStatus status "ACTIVE, INACTIVE, GRADUATED, WITHDRAWN, SUSPENDED"
        Instant createdAt
        Instant updatedAt
        long legacyStudentId
    }

    StudentGuardian {
        UUID studentId PK
        UUID guardianId PK
        boolean isPrimaryContact
        boolean isEmergencyContact
        boolean canPickupStudent
        String relationshipNotes
    }

C3.2 — REST API Structure

All endpoints follow the pattern established by the sample module.

Entity Base path Operations
AcademicHoliday /api/school/academic-holidays GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
AcademicPeriod /api/school/academic-periods GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
AcademicYear /api/school/academic-years GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}, GET /resolve
Campus /api/school/campuses GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
Classroom /api/school/classrooms GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
Family /api/school/families GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
Guardian /api/school/guardians GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
MySchool /api/school/my-school GET (list, paginated)
MyStaffProfile /api/school/my-staff-profile GET (list, paginated)
ParentChildren /api/school/parent/children GET (list, paginated)
School /api/school/schools GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
Section /api/school/sections GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
Staff /api/school/staff GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}
Student /api/school/students GET (list, paginated), GET /{id}, POST, PUT /{id}, DELETE /{id}

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/>school.*"]
    Kafka -->|consume| Listener

    subgraph Topics
        T1["school.schools"]
        T2["school.campuses"]
        T3["school.sections"]
        T4["school.classrooms"]
        T5["school.students"]
        T6["school.families"]
        T7["school.staff"]
        T8["school.guardians"]
        T9["school.academic-years"]
        T10["school.academic-periods"]
        T11["school.academic-holidays"]
    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
academicPeriodById entity UUID findById, update, create delete
familyById entity UUID findById, update, create delete
studentById entity UUID findById, update, create delete
campusById entity UUID findById, update, create delete
academicYearById entity UUID findById, update, create delete
guardianById entity UUID findById, update, create delete
schoolByLegacyId entity UUID findByLegacySchoolId ``
schoolById entity UUID findById, update, create delete
academicHolidayById entity UUID findById, update, create delete
staffById entity UUID findById, update, create delete
sectionById entity UUID findById, update, create delete
classroomById entity UUID findById, update, create 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 @Autowired fields)
  • 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)

Document Path
Sample module guide Sample Guide
Root PRD index docs/PRD.md
Backend system README backend/README.md