Skip to content

Project Structure ​

Monorepo Organization ​

The Campus project follows Nx monorepo conventions with clear separation between applications and shared libraries.

campus/
β”œβ”€β”€ apps/                    # Applications (orchestration layer)
β”œβ”€β”€ libs/                    # Shared libraries (decoupled modules)
β”œβ”€β”€ azure-pipelines/         # CI/CD configuration
└── sprint_planning/         # Project planning files

Architecture Evolution ​

Current State (Legacy - Being Phased Out) ​

  • Libraries have direct dependencies on other libraries
  • Complex dependency graph with tight coupling
  • Shared services imported directly between libs
  • State management scattered across multiple libs

Target State (Domain-Driven Design - Current Standard) ​

  • Complete domain isolation - each domain lib is entirely self-contained
  • Domain-owned state management - each domain lib manages its own NgRx state (actions, reducers, effects, selectors)
  • Domain-owned services - all services, interfaces, and injection tokens live within the domain
  • Zero inter-domain dependencies - domains cannot import from other domains
  • Apps as orchestration layer - apps wire up cross-domain communication through dependency injection
  • Interface segregation - each domain defines minimal interfaces for external dependencies only

Domain-Driven Design (DDD) Principles ​

Each domain library must be completely autonomous and include:

  1. State Management: Own NgRx store slice (actions, reducer, effects, selectors)
  2. Services: All business logic services within the domain
  3. Interfaces: Domain-specific interfaces and external dependency contracts
  4. Injection Tokens: All DI tokens for external dependencies
  5. Components/Directives: Domain-specific UI components
  6. Models/Types: All domain-related TypeScript interfaces and types
  7. Testing Utilities: Domain-specific mocks and test helpers

Migration Pattern ​

When working on domain libs, follow the DDD approach:

  1. Self-contained domains: All domain logic, state, and services stay within the domain lib
  2. External dependency interfaces: Define minimal interfaces for what the domain needs from outside
  3. Injection tokens: Create tokens for all external dependencies
  4. App-level wiring: Let apps provide implementations for external dependencies
  5. No cross-domain imports: Never import directly from other domain libs

Applications (apps/) ​

Main Applications ​

  • polpo-classroom-web - Primary classroom management interface (default project)
  • kabas-web - Kabas web application
  • kabas-student - Student-facing Kabas application
  • kabas-be - Kabas backend application
  • polpo-be - Polpo backend application

Specialized Tools ​

  • ink / ink-polpo - Digital drawing applications
  • speech-editor / speech-kabas / speech-polpo - Audio editing tools
  • text-editor - Rich text editing
  • timeline-editor - Interactive timeline creation
  • whiteboard-standalone - Digital whiteboard
  • cookie-consent - Cookie consent management
  • docs-components - Component documentation

E2E Testing ​

Each main application has a corresponding -e2e project for end-to-end testing.

Libraries (libs/) ​

Core Infrastructure ​

  • browser - Browser utilities and polyfills
  • environment - Environment configuration
  • utils - Common utilities
  • testing - Testing utilities and mocks
  • devlib - Development tools

UI & Components ​

  • components - Shared UI components
  • ui - Additional UI utilities
  • svg - SVG icon management
  • theming - Theme system and styling
  • animations - Animation utilities

Business Logic ​

  • dal - Data Access Layer
  • shared - Shared business logic
  • exercise - Exercise/assessment logic
  • adaptive-learning - Adaptive learning algorithms
  • search - Search functionality

Feature Libraries ​

  • pages/ - Page-specific components organized by feature
    • cms/ - Content management pages
    • settings/ - Settings pages
    • students/ - Student management
    • And many more...

External Integrations ​

  • external/excel - Excel integration
  • external/froala - Froala editor integration
  • learnosity - Learnosity assessment platform
  • scorm - SCORM compliance
  • cookies / cookies-consent - Cookie management

Specialized Features ​

  • speech-editor / speech-player - Audio content tools
  • timeline - Timeline functionality
  • whiteboard - Whiteboard features
  • rich-text - Rich text editing
  • loop - Learning loop functionality
  • ab-test - A/B testing framework

Dependency Rules ​

Current ESLint Rules (Legacy) ​

  • Libraries are tagged with dep: prefixes indicating their dependency level
  • Higher-level libraries can depend on lower-level ones
  • Applications can only depend on specific tagged libraries
  • Core libraries (browser, environment, utils) have minimal dependencies

Domain Dependency Pattern (DDD Standard) ​

  • Complete domain autonomy - each domain lib is entirely self-contained with its own state, services, and interfaces
  • No cross-domain dependencies - domains cannot import from other domains
  • Apps orchestrate cross-domain communication - only apps import and wire up multiple domains
  • Token-based external dependencies - domains define interfaces for external needs, apps provide implementations

Domain-Driven Design Pattern ​

Domain Structure ​

Each domain lib follows this self-contained structure:

libs/[domain-name]/src/lib/
β”œβ”€β”€ store/                   # NgRx state management
β”‚   β”œβ”€β”€ actions.ts          # Domain actions
β”‚   β”œβ”€β”€ reducer.ts          # Domain reducer
β”‚   β”œβ”€β”€ effects.ts          # Domain effects
β”‚   β”œβ”€β”€ selectors.ts        # Domain selectors
β”‚   └── index.ts            # Store exports
β”œβ”€β”€ services/               # Domain services
β”‚   β”œβ”€β”€ [domain].service.ts # Main domain service
β”‚   └── *.service.ts        # Supporting services
β”œβ”€β”€ interfaces/             # Domain interfaces
β”‚   β”œβ”€β”€ external/           # External dependency contracts
β”‚   β”‚   β”œβ”€β”€ auth.interface.ts
β”‚   β”‚   └── api.interface.ts
β”‚   β”œβ”€β”€ models/             # Domain models
β”‚   └── index.ts            # Interface exports
β”œβ”€β”€ tokens/                 # Injection tokens
β”‚   β”œβ”€β”€ external.tokens.ts  # External dependency tokens
β”‚   └── index.ts            # Token exports
β”œβ”€β”€ components/             # Domain components
β”œβ”€β”€ directives/             # Domain directives
β”œβ”€β”€ testing/                # Domain test utilities
└── index.ts                # Public API

External Dependency Interface Definition ​

Each domain defines minimal interfaces for external dependencies:

typescript
// libs/manage-tasks/src/lib/interfaces/external/auth.interface.ts
export interface TasksAuthService {
  getCurrentUser(): Observable<User>;
}

// libs/manage-tasks/src/lib/tokens/external.tokens.ts
export const TASKS_AUTH_SERVICE = new InjectionToken<TasksAuthService>('TasksAuthService');

Domain State Management ​

Each domain manages its own NgRx state:

typescript
// libs/manage-tasks/src/lib/store/actions.ts
export const TaskActions = createActionGroup({
  source: 'Tasks',
  events: {
    'Load Tasks': emptyProps(),
    'Load Tasks Success': props<{ tasks: Task[] }>(),
    'Load Tasks Failure': props<{ error: string }>(),
  },
});

// libs/manage-tasks/src/lib/store/reducer.ts
interface TasksState {
  tasks: Task[];
  loading: boolean;
  error: string | null;
}

export const tasksReducer = createReducer(/* ... */);

// libs/manage-tasks/src/lib/store/selectors.ts
export const selectTasksState = createFeatureSelector<TasksState>('tasks');
export const selectAllTasks = createSelector(selectTasksState, (state) => state.tasks);

App-Level Domain Orchestration ​

Apps wire up domains and provide external dependencies:

typescript
// apps/polpo-classroom-web/src/app/app.module.ts
@NgModule({
  imports: [
    // Domain modules
    ManageTasksModule,
    UserManagementModule,

    // Domain state registration
    StoreModule.forFeature('tasks', tasksReducer),
    StoreModule.forFeature('users', usersReducer),
    EffectsModule.forFeature([TasksEffects, UsersEffects]),
  ],
  providers: [
    // Shared services
    AuthService,
    ApiService,

    // Domain external dependency wiring
    {
      provide: TASKS_AUTH_SERVICE,
      useExisting: AuthService,
    },
    {
      provide: TASKS_API_SERVICE,
      useExisting: ApiService,
    },
    {
      provide: USERS_AUTH_SERVICE,
      useExisting: AuthService,
    },
  ],
})
export class AppModule {}

Domain Service Implementation ​

Domains consume external dependencies through their defined interfaces:

typescript
// libs/manage-tasks/src/lib/services/tasks.service.ts
@Injectable()
export class TasksService {
  constructor(
    @Inject(TASKS_AUTH_SERVICE) private authService: TasksAuthService,
    @Inject(TASKS_API_SERVICE) private apiService: TasksApiService,
    private store: Store
  ) {}

  loadTasks(): void {
    this.store.dispatch(TaskActions.loadTasks());
  }
}

File Naming Conventions ​

  • Components: kebab-case with campus- prefix
  • Directives: camelCase with campus prefix
  • Files: kebab-case.type.ts (e.g., user.service.ts)
  • Test files: *.spec.ts
  • E2E files: *.e2e-spec.ts

NgRx Organization (Domain-Driven) ​

Domain State Management ​

  • Domain-owned state: Each domain lib manages its own NgRx state slice
  • Co-located store files: Actions, reducer, effects, and selectors live within the domain
  • Feature state registration: Apps register domain state using StoreModule.forFeature()
  • Domain effects: Each domain handles its own side effects and API calls

Naming Conventions ​

  • Actions: Use domain-prefixed event naming: [DomainName] Event Description
  • Feature keys: Use domain name as feature key: 'tasks', 'users', 'ab-test'
  • Selectors: Prefix with domain: selectTasksState, selectAllTasks
  • Effects: Domain-specific effects: TasksEffects, UsersEffects

State Structure ​

typescript
// App-level state shape
interface AppState {
  tasks: TasksState; // Managed by tasks domain
  users: UsersState; // Managed by users domain
  abTest: ABTestState; // Managed by ab-test domain
}

Cross-Domain Communication ​

  • No direct state access: Domains cannot access other domains' state
  • App-level orchestration: Apps handle cross-domain communication
  • Event-driven: Use domain events for loose coupling
  • Service coordination: Apps coordinate between domain services when needed