# Architecture Overview The B12 SIS backend follows Clean Architecture principles with a layered design that separates concerns and promotes testability. ## High-Level Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ HTTP Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ Gin │ │ Routes │ │Middleware│ │ Controllers │ │ │ │ Router │─▶│ Setup │─▶│ Chain │─▶│ (Handlers) │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │ └─────────────────────────────────┬───────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Business Layer │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Services │ │ │ │ • Business logic │ │ │ │ • Validation │ │ │ │ • Authorization │ │ │ │ • Cross-cutting concerns │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────┬───────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Data Layer │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Repositories │ │ │ │ • CRUD operations │ │ │ │ • Query building │ │ │ │ • Transaction management │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ GORM + MySQL │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Directory Structure ``` b12-backend/ ├── cmd/ │ └── api/ │ └── main.go # Application entry point │ ├── internal/ │ ├── controllers/ # HTTP handlers (87 files) │ │ ├── base_controller.go # Generic CRUD base controller │ │ ├── auth_controller.go # Authentication endpoints │ │ ├── admin_controller.go # System administration │ │ ├── student_controller.go │ │ └── ... (70+ entity controllers) │ │ │ ├── services/ # Business logic (104 files) │ │ ├── base_service.go # Generic CRUD base service │ │ ├── auth_service.go # Authentication logic │ │ ├── auth_cache_service.go # User data caching │ │ ├── gradebook_calculation_service.go │ │ └── ... (entity services) │ │ │ ├── repositories/ # Data access (86 files) │ │ ├── base_repository.go # Generic CRUD base repository │ │ └── ... (entity repositories) │ │ │ ├── models/ # Domain entities (87 files) │ │ ├── base.go # BaseModel with common fields │ │ ├── users.go # User accounts │ │ ├── student.go # Student profiles │ │ ├── teachers.go # Teacher profiles │ │ └── ... (80+ domain models) │ │ │ ├── middlewares/ # HTTP middleware (5 files) │ │ ├── auth.go # JWT validation & user loading │ │ ├── audit.go # Security event tracking │ │ ├── api_log.go # API request logging │ │ ├── cors.go # CORS configuration │ │ └── language.go # i18n language selection │ │ │ ├── routes/ # Route registration │ │ └── routes.go # 1551 lines, 70+ entity routes │ │ │ ├── jobs/ # Background jobs (57 files, 114 jobs) │ │ ├── registry.go # Job registration factory │ │ ├── init.go # Job initialization │ │ ├── import_processor_job.go │ │ ├── canvas_sync_*.go # Canvas LMS sync jobs │ │ └── ... (gradebook, report, sync jobs) │ │ │ ├── hooks/ # External integrations │ │ └── canvas_hooks.go # Canvas LMS hooks │ │ │ ├── utils/ # Shared utilities (43 files) │ │ ├── filter.go # Filter parsing │ │ ├── response.go # HTTP response helpers │ │ ├── validation.go # Input validation │ │ ├── context.go # Context utilities │ │ ├── jwt.go # JWT token handling │ │ ├── i18n.go # Internationalization │ │ ├── canvas_integration.go # Canvas API client │ │ ├── s3.go # AWS S3 file upload │ │ └── ... │ │ │ ├── interfaces/ # Shared interfaces (6 files) │ │ ├── person.go # Person fields interface │ │ ├── address.go # Address fields interfaces │ │ ├── canvas_lms.go # Canvas LMS fields │ │ ├── mobile_app.go # Mobile app fields │ │ ├── searchable.go # Search interface │ │ └── team_filter.go # Team filtering interface │ │ │ └── types/ # Custom types │ └── date.go # Custom date type │ ├── locales/ # i18n translations │ ├── en.json │ └── vi.json │ ├── docs/ # Documentation │ └── helm/ # Kubernetes charts ``` ## Layer Responsibilities ### Controllers (HTTP Layer) Controllers handle HTTP requests and responses: - Parse request parameters and body - Validate input structure - Call appropriate service methods - Format and return responses - Handle HTTP-specific errors ```go func (c *StudentController) GetByID(ctx *gin.Context) { id := ctx.Param("id") student, err := c.service.GetByID(utils.GetContext(ctx), id) if err != nil { utils.ErrorResponse(ctx, http.StatusNotFound, "Student not found", err.Error()) return } utils.SuccessResponse(ctx, "Student retrieved", student) } ``` ### Services (Business Layer) Services contain business logic: - Business rule validation - Authorization checks - Orchestrating repository calls - Cross-cutting concerns (logging, audit) - External integrations ```go func (s *StudentService) Create(ctx context.Context, input StudentInput) (*Student, error) { // Business validation if err := s.validateStudent(input); err != nil { return nil, err } // Generate student code code, err := s.autoCodeService.GenerateCode("Student") if err != nil { return nil, err } student := mapInputToStudent(input) student.Code = code return s.repository.Create(ctx, student) } ``` ### Repositories (Data Layer) Repositories handle data persistence: - CRUD operations - Query building with filters - Transaction management - Preloading relationships ```go func (r *StudentRepository) FindByCode(ctx context.Context, code string) (*Student, error) { var student Student err := r.DB.WithContext(ctx). Where("code = ?", code). Preload("Class"). First(&student).Error return &student, err } ``` ## Generic CRUD Pattern The system uses Go generics for common CRUD operations: ``` BaseRepository[T] → BaseService[T, CreateInput, UpdateInput] → BaseController[T, CreateInput, UpdateInput] ``` This reduces boilerplate and ensures consistency. See [Generic CRUD Pattern](generic-crud.md) for details. ## Middleware Chain Requests flow through middleware in order: ``` Request → CORS → Auth → Team → AcademicSession → DataChange → Controller ``` | Middleware | Purpose | |------------|---------| | CORS | Handle cross-origin requests | | Auth | Validate JWT and extract user | | Team | Validate and set team context | | AcademicSession | Set academic session context | | DataChange | Audit data modifications | | Permission | Check user permissions | ## Database Design ### Soft Deletes All models use soft delete via `deleted_at` timestamp: ```go type BaseModel struct { Id string `gorm:"type:char(36);primary_key"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` } ``` ### Multi-tenancy Data isolation via `team_id`: ```go type BaseModel struct { // ... TeamId string `gorm:"size:36;index"` } ``` ### Audit Fields Every record tracks who created/modified it: ```go type BaseModel struct { // ... CreatedById string `gorm:"size:36;index"` UpdatedById string `gorm:"size:36;index"` DeletedById string `gorm:"size:36;index"` } ``` ## External Integrations ### Canvas LMS Integration via hooks pattern: ```go // Model AfterSave hook func (s *Student) AfterSave(tx *gorm.DB) error { go func() { if canvasHooks := hooks.GetCanvasHooks(); canvasHooks != nil { canvasHooks.SyncStudentIfNeeded(ctx, studentData, isUpdate) } }() return nil } ``` ### Background Jobs Jobs implement the `Job` interface: ```go type Job interface { Name() string Schedule() string // Cron expression Execute() error } ``` ## Configuration Configuration via environment variables with `.env` file support. See [Configuration Guide](../getting-started/configuration.md). ## Error Handling Consistent error handling pattern: ```go // Service layer - return errors if err := validate(input); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Controller layer - format response if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid input", err.Error()) return } ``` ## Testing Strategy - **Unit Tests**: Service and repository logic - **Integration Tests**: API endpoints with test database - **Table-driven Tests**: Go idiom for multiple test cases ```go func TestStudentService_Create(t *testing.T) { tests := []struct { name string input StudentInput wantErr bool }{ {"valid student", validInput, false}, {"missing name", missingNameInput, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := service.Create(ctx, tt.input) if (err != nil) != tt.wantErr { t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr) } }) } } ```