🚀 Adit API

Training Guide

🎯 Microservices
⚡ gRPC
📚 Event Sourcing
🔄 CQRS
🏗️ DDD

📈 Progressive Development Approach

Start Simple → Add Complexity → Master Architecture

About Your Instructor

👨‍💻

Dhaval Bhalodia

🎯 Role: Product architect at Adit

💼 Experience: 5+ years in microservices architecture

🔧 Expertise: Event Sourcing, CQRS, Domain-Driven Design

📚 Focus: Building scalable, maintainable systems

🎓 Mission: Empowering teams to build better software

"My goal is to help you understand not just the 'how' but the 'why' behind our architecture decisions"

Use video controls to play/pause

🏗️

Architecture Expert

Designed 50+ microservices

📈

Performance Focused

10x improvement in API response times

👥

Team Builder

Trained 100+ developers

Training Schedule Overview

📅 Day 1 - Foundations

Morning Session (10 AM - 1:30 PM)

  • ✅ Introduction to Microservices Architecture
  • ✅ Understanding gRPC & Protocol Buffers
  • ✅ Task Architecture & Folder Structure
  • ✅ Domain-Driven Design Principles

Afternoon Session (2:30 PM - 6:30 PM)

  • ✅ Progressive Development - Phase 1
  • ✅ Creating Your First Endpoint
  • ✅ Understanding @Adit Decorators
  • ✅ Hands-on Practice
📅 Day 2 - Advanced Concepts

Morning Session (10 AM - 1:30 PM)

  • ✅ Command Pattern (Phase 2)
  • ✅ Event Sourcing (Phase 3)
  • ✅ Testing Strategies
  • ✅ Common Issues & Debugging

Afternoon Session (2:30 PM - 6:30 PM)

  • ✅ Advanced Patterns from Production
  • ✅ Team Collaboration
  • ✅ Q&A Session
  • ✅ Next Steps & Resources

🎯 Training Goal

By the end of this training, you'll be able to independently create, test, and deploy microservices following our architecture patterns.

📅 Day 1 - Morning Session (10 AM - 1:30 PM)

10:00 - 10:30 AM: Welcome & Introduction

  • Team introductions
  • Training objectives & expectations
  • Overview of Adit architecture

10:30 - 11:30 AM: Microservices & gRPC

  • Why microservices?
  • gRPC vs REST comparison
  • Protocol Buffers deep dive
  • Live demo: Creating your first proto file

11:30 - 11:45 AM: Break ☕

11:45 AM - 12:45 PM: Task Architecture

  • Understanding folder structure
  • Domain vs Application layers
  • Hands-on: Creating task structure

12:45 - 1:30 PM: Domain-Driven Design

  • DDD principles in practice
  • Aggregates, Entities, Value Objects
  • Q&A session

📅 Day 1 - Afternoon Session (2:30 PM - 6:30 PM)

2:30 - 4:00 PM: Phase 1 Implementation

  • Live coding: Basic endpoint
  • Creating domain models
  • Repository pattern
  • Wiring everything together

4:00 - 4:15 PM: Break ☕

4:15 - 5:30 PM: @Adit Decorators

  • Understanding the magic
  • Common decorator mistakes
  • Debugging decorator issues

5:30 - 6:30 PM: Hands-on Practice

  • Individual task creation
  • Troubleshooting together
  • Day 1 recap & homework

Quick Introduction

🎯 What is Adit API?

  • 🏗️ Microservices project - multiple small services instead of one big application
  • 🔲 Each service handles one domain (Patient Service, Appointment Service, etc.)
  • ⚡ Services communicate using gRPC (like REST API but faster and with contracts)
  • 📚 We use Event Sourcing - storing all changes as events (like a history log)
  • 🔄 CQRS = Separate models for reading and writing data
🏗️

Domain-Driven Design

Business logic first

High Performance

Binary protocol, type-safe

📝

Complete Audit Trail

Every change is tracked

Understanding gRPC and Protocol Buffers

⚡ What is gRPC?

gRPC is a high-performance RPC (Remote Procedure Call) framework. Think of it as:

  • 📦 Like REST but faster - Uses binary instead of JSON
  • 📝 Contract-first - API is defined in .proto files
  • 🔒 Type-safe - Generated code ensures type safety
  • 🚀 Efficient - Smaller payloads, faster parsing
Client
Proto Contract
Server

🎯 Key Benefit

gRPC is up to 10x faster than REST APIs!

Protocol Buffers (.proto files)

Proto files define your API contract. They specify:

  • 🔧 What methods are available
  • 📤 What data can be sent (request)
  • 📥 What data comes back (response)

Example Proto File

syntax = "proto3";

package patient;

// Service definition
service PatientSrv {
  // Method definition
  rpc PatientDelete(PatientDeleteRequest) returns (PatientDeleteResponse);
}

// Request message
message PatientDeleteRequest {
  string patientId = 1;
  string organizationId = 2;
  string deletedBy = 3;
}

// Response message
message PatientDeleteResponse {
  bool success = 1;
  string patientId = 2;
  optional string error = 3;
}

📝 Note

Proto file defines the API contract. Service and method names here will be used in @GrpcMethod.

How Proto Maps to Your Code

🔗 From Proto to TypeScript

// In your controller:
@GrpcMethod('PatientSrv', 'PatientDelete')  // Must match proto!
async patientDelete(payload: PatientDeleteRequest) {
  // payload will have: patientId, organizationId, deletedBy
}

⚠️ Important!

  • Service name in @GrpcMethod must match proto service name
  • Method name must match proto rpc name
  • Payload structure must match proto message

✅ Best Practice

Always create your proto file FIRST, then implement the code to match it!

Understanding Task Architecture

📦 What is a "Task"?

A task represents one specific operation or endpoint in your service. For example:

  • patient-create_task = Creating a new patient
  • patient-update_task = Updating patient information
  • patient-getbyid_task = Fetching a specific patient
  • patient-delete_task = Deleting a patient

Each task is self-contained with its own business logic, data access, and API endpoint.

🎯 Single Responsibility

One task = One operation

📦 Self-Contained

All related code in one place

🔧 Easy to Test

Isolated functionality

Task Folder Structure

patient-create_task/ ├── domain/ # Business Logic Layer (Pure, No Dependencies) │ ├── models/ # Core business entities │ │ ├── *.aggregate.ts # Main entity with business rules │ │ └── *.domain.model.ts # Base model with properties │ ├── events/ # Things that happened │ │ ├── imp/ # Event implementations │ │ └── handler/ # Event handlers │ ├── value-objects/ # Strongly typed domain concepts │ └── exceptions/ # Business rule violations │ ├── application/ # Application Logic Layer (Orchestration) │ ├── commands/ # Instructions to change data │ │ ├── imp/ # Command definitions │ │ └── handler/ # Command execution logic │ ├── queries/ # Instructions to read data │ ├── repositories/ # Data access interfaces │ └── dtos/ # Data transfer objects │ ├── *.controller.ts # gRPC endpoint (Entry point) ├── *.service.ts # Service orchestration ├── *.module.ts # NestJS module definition └── *.resources.ts # Resource configuration

Why This Structure?

🎯 Separation of Concerns

Business logic (domain) is separate from infrastructure (application)

🧪 Testability

Each layer can be tested independently

🔧 Maintainability

Easy to find and modify specific functionality

📈 Scalability

New features don't affect existing code

Request Flow:
Controller
Service
Application
Domain

Domain Folder Explained

The domain folder contains your pure business logic. This is the heart of your application.

🎯 Key Principles

  • 🚫 No framework dependencies - Pure TypeScript/JavaScript
  • 📋 Business rules only - How a patient can be created, updated, deleted
  • 📖 Self-documenting - The code explains the business

📦 Models

Aggregate: Main business entity
Domain Model: Base properties

📨 Events

Records of things that happened
(PatientCreated, PatientDeleted)

💎 Value Objects

Strongly typed concepts
(Email, PhoneNumber, PatientId)

❌ Exceptions

Business rule violations
(PatientAlreadyExists, InvalidAge)

Application Folder Explained

The application folder orchestrates the domain logic and handles infrastructure concerns.

🎯 Key Principles

  • 📦 Uses domain objects - Never duplicates business logic
  • 🔧 Handles infrastructure - Database access, external services
  • 🔄 Coordinates operations - Combines multiple domain operations

📝 Commands

imp/ Command definitions
handler/ Execution logic

🔍 Queries

imp/ Query definitions
handler/ Query execution

💾 Repositories

Data access layer
Loads and saves aggregates

📦 DTOs

Data Transfer Objects
Request/Response shapes

💡 The imp/ and handler/ Pattern

  • imp/ = "Implementation" - The WHAT (data structure)
  • handler/ = The HOW (execution logic)

Progressive Development Approach

We'll build features in three phases to make learning easier:

Phase 1

Basic Working Endpoint

Goal: Get something working quickly

  • Create domain model (without events)
  • Create repository for data access
  • Wire up controller, service, and module

✅ Result: A working endpoint you can test!

Phase 2

Add Command Pattern

Goal: Improve architecture with CQRS

  • Create command definition
  • Move logic to command handler
  • Service becomes thin orchestrator

✅ Result: Better separation of concerns!

Phase 3

Add Event Sourcing

Goal: Complete audit trail

  • Create domain events
  • Update aggregate to emit events
  • Events are automatically saved

✅ Result: Full history of all changes!

Understanding @Adit Decorators

🔥 CRITICAL: The @Adit decorator is the MAGIC that makes everything work!

Without it, your components won't be registered!

What Does @Adit Do?

The @Adit decorator registers your components with the framework so they can be:

  • ✅ Found by dependency injection
  • ✅ Connected to the right service
  • ✅ Properly initialized

RegisterRepository

@Adit({
  srvName: AditService.SrvNames.PATIENT_SRV,
  type: 'RegisterRepository',
})

RegisterCommandHandler

@Adit({
  srvName: AditService.SrvNames.PATIENT_SRV,
  type: 'RegisterCommandHandler',
})

RegisterEvent

@Adit({
  srvName: AditService.SrvNames.PATIENT_SRV,
  type: 'RegisterEvent',
})

CRITICAL: Always Include @Adit!

❌ Common Error: "Command handler not found"

Cause: Missing @Adit decorator

❌ WRONG - Missing @Adit

export class PatientDeleteTaskCommandHandler {
  // Handler won't be found!
}

✅ CORRECT - With @Adit

@Adit({
  srvName: AditService.SrvNames.PATIENT_SRV,
  type: 'RegisterCommandHandler',
})
export class PatientDeleteTaskCommandHandler {
  // Handler will work!
}

💡 Remember

No @Adit = Component not registered = Errors!

🍽️ Lunch Break

Day 1 - Morning Session Complete

Great job! Take a break and recharge.

⏰ 1:30 PM - 2:30 PM

See you back at 2:30 PM for the afternoon session!

💡 Tip: Review what we've learned so far and prepare your questions

Creating a New Endpoint - Progressive Approach

Let's create a new endpoint to delete a patient using our progressive approach.

🎯 What We'll Build

  • 📝 Proto file defining the API contract
  • 🏗️ Domain model with business rules
  • 💾 Repository for data access
  • 🎮 Controller to handle gRPC requests
  • ⚙️ Service to orchestrate operations
  • 📦 Module to wire everything together
We'll start simple and add complexity:
Phase 1
Basic
Phase 2
Commands
Phase 3
Events

Phase 1 Step 1: Create Proto File First

File: patient-delete_task/proto/patient-delete.task.proto

syntax = "proto3";

package patient;

service PatientSrv {
  rpc PatientDelete(PatientDeleteRequest) returns (PatientDeleteResponse);
}

message PatientDeleteRequest {
  string patientId = 1;
  string organizationId = 2;
  string deletedBy = 3;
}

message PatientDeleteResponse {
  bool success = 1;
  string patientId = 2;
  optional string error = 3;
}

📝 Note

Proto file defines the API contract. Service and method names here will be used in @GrpcMethod.

Phase 1 Step 2 & 3: Create Structure & Domain Model

📁 Folder Structure
🏗️ Domain Model

Step 2: Create Task Folder Structure

cd apps/patient_srv/src/
mkdir -p patient-delete_task/proto
mkdir -p patient-delete_task/domain/models
mkdir -p patient-delete_task/application/repositories
# We'll create more folders later as we need them

Step 3: Create the Domain Model (Simple Version)

File: patient-delete_task/domain/models/patient-delete.task.aggregate.ts

import { AggregateRoot } from '@adit/core/event';

export class PatientDeleteTaskAggregate extends AggregateRoot {
  private isDeleted: boolean = false;
  private deletedBy?: string;
  private deletedAt?: Date;

  // Simple business logic method (no events yet)
  markAsDeleted(deletedBy: string): void {
    if (this.getIsDeleted()) {
      throw new Error('Patient is already deleted');
    }
    
    // For now, just update the state directly
    this.isDeleted = true;
    this.deletedBy = deletedBy;
    this.deletedAt = new Date();
  }
  
  // Helper method to check status
  getIsDeleted(): boolean {
    return this.isDeleted;
  }
}

Phase 1 Step 4: Create the Repository

File: patient-delete_task/application/repositories/patient-delete.task.repository.ts

import { Adit } from '@adit/decorators';
import { AditService } from '@adit/util';
import { BaseMultitenantRepository } from '@adit/core/common';
import { PatientDeleteTaskAggregate } from '../../domain/models/patient-delete.task.aggregate';

@Adit({
  srvName: AditService.SrvNames.PATIENT_SRV,
  type: 'RegisterRepository',
})
export class PatientDeleteTaskRepository extends BaseMultitenantRepository<PatientDeleteTaskAggregate> {
  constructor() {
    super(PatientDeleteTaskAggregate);
  }
  
  async findById(patientId: string): Promise<PatientDeleteTaskAggregate | null> {
    // For now, let's create a mock patient
    // In real code, this would load from database
    const patient = new PatientDeleteTaskAggregate();
    patient.id = patientId;
    return patient;
  }
  
  async save(patient: PatientDeleteTaskAggregate): Promise<void> {
    // For now, just log
    console.log('Saving patient:', patient.id, 'isDeleted:', patient.getIsDeleted());
    // In real code, this would save to database
  }
}

⚠️ Don't forget the @Adit decorator!

Phase 1 Steps 5-7: Module, Service & Controller

📦 Module
⚙️ Service
🎮 Controller

Step 5: Create the Module

import { Module } from '@nestjs/common';
import { PatientDeleteTaskController } from './patient-delete.task.controller';
import { PatientDeleteTaskService } from './patient-delete.task.service';
import { PatientDeleteTaskRepository } from './application/repositories/patient-delete.task.repository';

@Module({
  controllers: [PatientDeleteTaskController],
  providers: [
    PatientDeleteTaskService,
    PatientDeleteTaskRepository,  // Add repository
  ],
})
export class PatientDeleteTaskModule {}

Step 6: Create the Service (Simple Version)

import { Injectable } from '@nestjs/common';
import { PatientDeleteTaskRepository } from './application/repositories/patient-delete.task.repository';

@Injectable()
export class PatientDeleteTaskService {
  constructor(
    private readonly repository: PatientDeleteTaskRepository,
  ) {}

  async deletePatient(payload: any) {
    console.log('=== SERVICE: Processing delete ===');
    
    // Validate required fields
    if (!payload.patientId || !payload.organizationId) {
      throw new Error('Missing required fields');
    }
    
    // Load patient
    const patient = await this.repository.findById(payload.patientId);
    if (!patient) {
      throw new Error('Patient not found');
    }
    
    // Apply business logic
    patient.markAsDeleted(payload.deletedBy || 'system');
    
    // Save changes
    await this.repository.save(patient);
    
    console.log('=== Delete completed ===');
    return { success: true, patientId: payload.patientId };
  }
}

Step 7: Create the Controller

import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { PatientDeleteTaskService } from './patient-delete.task.service';

@Controller()
export class PatientDeleteTaskController {
  constructor(private readonly service: PatientDeleteTaskService) {}

  @GrpcMethod('PatientSrv', 'PatientDelete')
  async patientDelete(payload: any) {
    console.log('=== DELETE REQUEST RECEIVED ===', payload);
    return this.service.deletePatient(payload);
  }
}

🎉 Checkpoint: You Have a Working Endpoint!

Step 8: Register in Main Module

File: apps/patient_srv/src/patient.module.ts

import { PatientDeleteTaskModule } from './patient-delete_task/patient-delete.task.module';

@Module({
  imports: [
    // ... other modules
    PatientDeleteTaskModule,  // Add this line
  ],
})
export class PatientModule {}

✅ At this point, you can:

  • Start your service: npm run start:patient_srv:dev
  • Call the PatientDelete endpoint
  • See logs in console
  • Business logic is enforced (can't delete twice)
Current flow:
Controller
Service
Repository
Domain Model

This works! But let's improve the architecture...

🎉 Day 1 Complete!

Excellent Progress Today!

What We Covered:

  • ✅ Microservices & gRPC fundamentals
  • ✅ Task architecture & folder structure
  • ✅ Domain-Driven Design principles
  • ✅ Phase 1: Basic endpoint implementation
  • ✅ @Adit decorators mastery

📚 Homework: Try creating another simple endpoint using Phase 1 approach

See you tomorrow at 10:00 AM for Day 2!

📅 Day 2 - Morning Session (10 AM - 1:30 PM)

10:00 - 11:15 AM: Command Pattern (Phase 2)

  • CQRS principles
  • Command vs Query separation
  • Live refactoring to commands
  • Benefits demonstration

11:15 - 11:30 AM: Break ☕

11:30 AM - 12:30 PM: Event Sourcing (Phase 3)

  • Event sourcing concepts
  • Creating domain events
  • Event handlers & projections
  • Viewing event store data

12:30 - 1:30 PM: Testing Strategies

  • Unit testing aggregates
  • Testing command handlers
  • E2E testing approaches
  • Coverage requirements

Phase 2 Add Command Pattern for Better Architecture

Now we'll refactor to use the Command pattern. This separates concerns better.

Step 9 & 10: Create Command Structure

mkdir -p patient-delete_task/application/commands/imp
mkdir -p patient-delete_task/application/commands/handler

Create the Command

File: patient-delete_task/application/commands/imp/patient-delete.command.ts

import { ICommand } from '@adit/core/event';

export class PatientDeleteCommand implements ICommand {
  constructor(
    public readonly patientId: string,
    public readonly organizationId: string,
    public readonly deletedBy: string,
  ) {}
}

💡 What is a Command?

A data structure representing an instruction to change something

🎯 Why Use Commands?

Separates the "what" from the "how" - better testing & organization

Phase 2 Step 11: Create Command Handler

File: patient-delete_task/application/commands/handler/patient-delete.task.command.handler.ts

import { Adit } from '@adit/decorators';
import { AditService } from '@adit/util';
import { ICommandHandler } from '@adit/core/event';
import { PatientDeleteCommand } from '../imp/patient-delete.command';
import { PatientDeleteTaskRepository } from '../../repositories/patient-delete.task.repository';

@Adit({
  srvName: AditService.SrvNames.PATIENT_SRV,
  type: 'RegisterCommandHandler',
})
export class PatientDeleteTaskCommandHandler implements ICommandHandler<PatientDeleteCommand> {
  constructor(
    private readonly repository: PatientDeleteTaskRepository,
  ) {}

  async execute(command: PatientDeleteCommand): Promise<any> {
    console.log('=== COMMAND HANDLER: Deleting patient ===');
    
    // Step 1: Load the patient aggregate
    const patient = await this.repository.findById(command.patientId);
    
    if (!patient) {
      throw new Error('Patient not found');
    }
    
    // Step 2: Call domain method to delete
    patient.markAsDeleted(command.deletedBy);
    
    // Step 3: Save the changes
    await this.repository.save(patient);
    
    return { success: true };
  }
}

⚠️ Don't forget the @Adit decorator on the handler!

Phase 2 Steps 12-13: Update Service & Module

⚙️ Updated Service
📦 Updated Module

Step 12: Update Service to Use Commands

import { Injectable } from '@nestjs/common';
import { PatientBaseService } from '../patient-base.service';
import { CommandBus } from '@adit/core/event';
import { PatientDeleteCommand } from './application/commands/imp/patient-delete.command';

@Injectable()
export class PatientDeleteTaskService extends PatientBaseService {
  constructor(protected readonly commandBus: CommandBus) {
    super(commandBus);
  }

  async deletePatient(payload: any) {
    console.log('=== SERVICE: Processing delete ===');
    
    // Validate required fields
    this.validateRequired(payload, ['patientId', 'organizationId']);
    
    // Create and execute command
    const command = new PatientDeleteCommand(
      payload.patientId,
      payload.organizationId,
      payload.deletedBy || 'system'
    );
    
    await this.commandBus.execute(command);
    
    console.log('=== Delete completed ===');
    return { success: true, patientId: payload.patientId };
  }
}

Step 13: Update Module to Include Command Handler

import { Module } from '@nestjs/common';
import { PatientDeleteTaskController } from './patient-delete.task.controller';
import { PatientDeleteTaskService } from './patient-delete.task.service';
import { PatientDeleteTaskRepository } from './application/repositories/patient-delete.task.repository';
import { PatientDeleteTaskCommandHandler } from './application/commands/handler/patient-delete.task.command.handler';

@Module({
  controllers: [PatientDeleteTaskController],
  providers: [
    PatientDeleteTaskService,
    PatientDeleteTaskRepository,
    PatientDeleteTaskCommandHandler,  // Add command handler
  ],
})
export class PatientDeleteTaskModule {}

✅ Benefits of Command Pattern

  • Service is now thin (just validation and command creation)
  • Business logic is in the handler
  • Easier to test each component
  • Better separation of concerns

Phase 3 Add Event Sourcing

Finally, let's add events to track all changes.

Steps 14-15: Create Event Structure & Event

mkdir -p patient-delete_task/domain/events/imp

File: patient-delete_task/domain/events/imp/patient-deleted.event.ts

import { Adit } from '@adit/decorators';
import { AditService } from '@adit/util';
import { Event, IEvent } from '@adit/core/event';

@Adit({
  srvName: AditService.SrvNames.PATIENT_SRV,
  type: 'RegisterEvent',
})
@Event('PATIENT_DELETED')
export class PatientDeletedEvent implements IEvent {
  constructor(
    public readonly data: {
      patientId: string;
      deletedBy: string;
      deletedAt: Date;
    }
  ) {}
}

📚 What is Event Sourcing?

Instead of storing just the current state, we store all events that led to that state. This gives us a complete audit trail!

Phase 3 Step 16: Update Aggregate to Use Events

File: patient-delete_task/domain/models/patient-delete.task.aggregate.ts

import { AggregateRoot, EventHandler } from '@adit/core/event';
import { PatientDeletedEvent } from '../events/imp/patient-deleted.event';

export class PatientDeleteTaskAggregate extends AggregateRoot {
  private isDeleted: boolean = false;
  private deletedBy?: string;
  private deletedAt?: Date;

  // Updated to use events
  markAsDeleted(deletedBy: string): void {
    if (this.isDeleted) {
      throw new Error('Patient is already deleted');
    }
    
    // Now we apply an event instead of direct state change
    this.apply(new PatientDeletedEvent({
      patientId: this.id,
      deletedBy: deletedBy,
      deletedAt: new Date(),
    }));
  }
  
  // Event handler updates the state
  @EventHandler(PatientDeletedEvent)
  onPatientDeleted(event: PatientDeletedEvent): void {
    this.isDeleted = true;
    this.deletedBy = event.data.deletedBy;
    this.deletedAt = event.data.deletedAt;
  }
  
  getIsDeleted(): boolean {
    return this.isDeleted;
  }
}
Event Flow:
apply(event)
Event Handler
Update State
Save to Event Store

🍽️ Lunch Break

Day 2 - Morning Session Complete

Excellent progress with advanced patterns!

⏰ 1:30 PM - 2:30 PM

Final session starts at 2:30 PM!

💡 We've covered Phases 2 & 3 - Command Pattern & Event Sourcing!

📅 Day 2 - Afternoon Session (2:30 PM - 6:30 PM)

2:30 - 3:30 PM: Advanced Production Patterns

  • Security & authentication
  • Cross-service communication
  • Performance optimization
  • Real-world case studies

3:30 - 3:45 PM: Break ☕

3:45 - 5:00 PM: Team Collaboration

  • Code review best practices
  • Git workflow & branching
  • Documentation standards
  • Team communication

5:00 - 6:00 PM: Q&A & Problem Solving

  • Open discussion
  • Specific use case questions
  • Architecture decisions

6:00 - 6:30 PM: Next Steps

  • Resources & documentation
  • Practice assignments
  • Support channels
  • Closing remarks

🎆 Final Checkpoint: Full Event Sourcing!

Complete flow:
Controller
Service
Command
Handler
Repository
Aggregate
Event

📝 What happens now:

  1. Controller receives request
  2. Service validates and creates command
  3. Command handler loads aggregate
  4. Aggregate applies business rules and emits event
  5. Event is automatically saved to event store
  6. State is updated through event handler
  7. Complete audit trail is maintained

🔍 To see events in database:

SELECT * FROM event_store WHERE aggregate_id = 'patient-123';

Visual Flow Diagrams

📊 Simple Flow (After Phase 1)

┌──────────────┐     ┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  Controller  │────▶│   Service   │────▶│  Repository  │────▶│   Domain    │
│  (gRPC)      │     │ (Validates) │     │ (Data Access)│     │ (Business)  │
└──────────────┘     └─────────────┘     └──────────────┘     └─────────────┘

🔄 Command Flow (After Phase 2)

┌──────────────┐     ┌─────────────┐     ┌─────────────┐
│  Controller  │────▶│   Service   │────▶│   Command   │
└──────────────┘     └─────────────┘     └─────────────┘
                                                │
                                                ▼
┌──────────────┐     ┌─────────────┐     ┌─────────────┐
│   Domain     │◀────│ Repository  │◀────│   Handler   │
└──────────────┘     └─────────────┘     └─────────────┘

📚 Event Sourcing Flow (After Phase 3)

┌──────────────┐     ┌─────────────┐
│   Domain     │────▶│    Event    │
│  (Aggregate) │     └─────────────┘
└──────────────┘            │
                            ▼
                    ┌─────────────┐
                    │ Event Store │
                    └─────────────┘

Common Issues and Solutions

Issue 1: "Cannot find module"

Solution: Make sure you:

  • Created all files with exact names
  • Registered module in patient.module.ts
  • Restarted the service

Issue 2: "Method not found"

Solution: Check:

  • Method name in @GrpcMethod matches .proto
  • Proto file has the method defined
  • Service is running on correct port

Issue 3: "Command handler not found"

Solution: Make sure:

  • @Adit decorator is present on handler
  • Handler implements ICommandHandler
  • Service name in decorator is correct

Issue 4: "Repository not injected"

Solution: Check:

  • @Adit decorator on repository class
  • Repository added to module providers
  • Service name matches in decorator

🔧 Debugging Tips

  • Add console.logs at each step
  • Check database directly
  • Test with simple payloads first

Writing Test Cases - CRITICAL FOR TEAM LEADS

🎯 Why Testing is Important

  • 🐛 Catches bugs early - Before they reach production
  • 📖 Documents your code - Tests show how your code should work
  • 🔄 Enables refactoring - Change code confidently when tests pass
  • Required for all tasks - No feature is complete without tests
patient-delete_task/ ├── __tests__/ │ ├── patient-delete.task.service.spec.ts # Service tests │ ├── patient-delete.task.handler.spec.ts # Command handler tests │ ├── patient-delete.task.aggregate.spec.ts # Domain logic tests │ └── patient-delete.task.e2e.spec.ts # End-to-end tests

📊 Coverage Requirements

  • Minimum 80% coverage required
  • All business logic must be tested
  • Edge cases must be covered

🚀 Advanced Patterns from Production

Real-world patterns we've implemented and tested in production environments

🎯 Production-Ready Patterns

  • 🔐 Authentication & Security Architecture
  • 🔗 Cross-Service Communication Security
  • 📦 Package Management Best Practices
  • 📚 Advanced Event Sourcing Patterns
  • 🚀 Production Deployment Strategies
  • Performance Optimization
  • 🧪 Testing Strategies for Production

💡 Experience Matters

These patterns have been refined through real production deployments, handling thousands of requests per second

🎯 Summary - Key Takeaways

🏗️ Architecture Principles

  • Progressive Development - Start simple, evolve to complex
  • 🎯 Domain-Driven Design - Business logic in aggregates
  • 📚 Event Sourcing - Complete audit trail of all changes
  • 🔄 CQRS Pattern - Separate commands from queries

🛠️ Implementation Guidelines

  • 📁 Consistent folder structure - Always follow the pattern
  • 🏷️ @Adit decorator - Essential for auto-registration
  • 🧪 Test-first approach - No code without tests
  • 📊 80% minimum coverage - 100% for business logic

🚀 Remember: Quality > Speed

Take time to understand each phase. Better to build right than to rebuild.

🎊 You're Ready to Build!

🌟
You're now ready to build features in the Adit API!

"Start with Phase 1 and work your way up.
Remember: Simple → Working → Better → Best"

🎯 Next Steps

  1. Try creating a "change patient status" endpoint WITH TESTS
  2. Write tests BEFORE implementing features (TDD)
  3. Run tests after every change
  4. Aim for 100% coverage on business logic
  5. Review test examples in existing tasks

📋 Remember

❓ Frequently Asked Questions

Q: How long does it take to become proficient with this architecture?
A: With the progressive approach, you can build your first endpoint in 1-2 days. Full proficiency typically takes 2-3 weeks of hands-on practice.
Q: What if I forget to add the @Adit decorator?
A: The component won't be registered and you'll get runtime errors. Always check for @Adit decorators when debugging "not found" errors.
Q: Should I start with Event Sourcing right away?
A: No! Follow the progressive approach: Phase 1 (Basic) → Phase 2 (Commands) → Phase 3 (Events). This helps you understand each layer's value.
Q: How do I debug gRPC endpoints?
A: Use console.logs at each layer, check the proto file matches your @GrpcMethod, and test with simple payloads first.
Q: What's the minimum test coverage required?
A: 80% overall, but aim for 100% on business logic (domain and command handlers). No feature is complete without tests!

💪 Team Strength & Collaboration

"Alone we can do so little; together we can do so much."

- Helen Keller

🤝 Our Team Values

  • Collaboration over Competition
  • 📚 Continuous Learning
  • 🎯 Quality over Quantity
  • 🔄 Share Knowledge Freely
  • 🚀 Innovate Together

Remember

Every expert was once a beginner. Don't hesitate to ask questions!

Use video controls to play/pause

🏆

Together We Achieve More

Your success is our success!

🙏 Thank You!

"The best way to learn is by doing. Start with Phase 1 and build your confidence step by step."

Let's Stay Connected

Feel free to reach out with any questions or for code reviews!