KaroKar is structured as a Domain-Driven Modular Monolith — ten bounded contexts, each owning its own business rules, running inside a single deployable process. The inevitable challenge with any multi-domain system is cross-cutting workflows: a booking approval must update vehicle availability, trigger a notification, and create an audit record all at once. Without a deliberate communication strategy, this turns into a web of tightly coupled service-to-service calls that are hard to test, hard to extend, and nearly impossible to extract into independent services later. KaroKar solves this with an event-driven communication strategy: every significant business action results in a domain event, and other bounded contexts react to that event independently.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Codefied-CodePix/Karokar-backend/llms.txt
Use this file to discover all available pages before exploring further.
Core Principle
A domain owns its business rules. Other domains may react to those rules through events — they may never reach in and execute another domain’s business logic directly.Domains communicate through facts, not commands.Instead of
BookingService calling AvailabilityService.reserve(), the Booking Domain says “A booking was approved” and the Availability Domain decides on its own what to do with that information.
The DomainEvent Base Class
Every event in KaroKar extends the same abstractDomainEvent class, guaranteeing a consistent envelope on all published facts.
| Field | Purpose |
|---|---|
eventId | Globally unique identifier — used for idempotency checks |
eventType | Human-readable event name, e.g. "BookingApproved" |
aggregateType | The domain aggregate that produced the event, e.g. "Booking" |
aggregateId | The specific aggregate instance ID |
occurredAt | Wall-clock timestamp of when the business fact occurred |
version | Schema version — incremented when the payload shape changes |
payload | Event-specific data (typed by each concrete subclass) |
A Real Event: BookingApprovedEvent
Concrete events are thin wrappers aroundDomainEvent. They set the fixed eventType and aggregateType values while accepting the domain-specific payload from the service layer.
Phase 01 Event Bus: NestJS EventEmitter
In Phase 01 KaroKar uses an in-process event bus — NestJS’s built-inEventEmitter2 module. Events are published and consumed inside the same application process with no network hop and no external broker. This is intentional: the current business scale does not justify the operational overhead of Kafka, RabbitMQ, or SNS/SQS, and the in-process bus is fully sufficient for the modular monolith architecture.
Producer → Consumer Flow
The canonical example is booking approval:Booking Domain approves a booking
The
BookingService completes its state transition and emits a BookingApprovedEvent onto the event bus.Availability Domain reacts
An
@OnEvent('BookingApproved') listener creates an availability block for the vehicle, preventing double-bookings.Notification Domain reacts
A separate listener sends a confirmation email to the Corporate Admin and an alert to the Vendor Admin.
Critical Domain Events
Booking Domain
Booking Domain
| Event | Triggered When |
|---|---|
BookingRequested | A Corporate Admin creates a new booking request |
BookingApproved | A Vendor Admin approves the request |
BookingRejected | A Vendor Admin rejects the request |
BookingAssigned | A vehicle is assigned to the booking |
BookingActivated | The booking period begins |
BookingCompleted | The booking period ends successfully |
BookingCancelled | Cancelled before activation |
BookingTerminated | Forcefully closed mid-booking |
Assignment Domain
Assignment Domain
| Event | Triggered When |
|---|---|
AssignmentCreated | A Corporate Admin assigns a vehicle to an employee |
AssignmentAccepted | The employee accepts the assignment |
AssignmentRejected | The employee rejects the assignment |
AssignmentClosed | The assignment period concludes |
Fleet Domain
Fleet Domain
| Event | Triggered When |
|---|---|
VehicleCreated | A new vehicle is registered by a Vendor |
VehicleUpdated | Vehicle details are modified |
VehicleActivated | A vehicle is cleared for bookings |
VehicleSuspended | A vehicle is taken out of service |
VehicleMaintenanceScheduled | Maintenance window is created |
VehicleMaintenanceCompleted | Maintenance window closes |
Verification Domain
Verification Domain
| Event | Triggered When |
|---|---|
VerificationRequested | An entity submits documents for review |
VerificationApproved | Platform Admin approves the submission |
VerificationRejected | Platform Admin rejects the submission |
VerificationExpired | A validity period elapses |
VerificationRevoked | A verification is cancelled due to fraud or compliance breach |
Organization Domain
Organization Domain
| Event | Triggered When |
|---|---|
OrganizationCreated | A new organization completes registration |
OrganizationApproved | Platform Admin approves the organization |
OrganizationSuspended | Platform Admin suspends the organization |
Event Consumption Rules
Permitted consumer actions:- ✅ Update read models
- ✅ Create audit records
- ✅ Send notifications
- ✅ Create or release availability records
- ✅ Generate analytics
- ❌ Modify the producer aggregate’s persistent state
- ❌ Call the producer domain’s service methods
Event Idempotency
Consumers must be idempotent. Because the same event may be delivered more than once — particularly as the transport layer evolves toward distributed brokers — every consumer must guard against duplicate processing. ABookingApproved event processed twice must not create two availability blocks or send two emails. Consumers should use the eventId field to deduplicate events where necessary.
Event Versioning
Events are public contracts. When a payload must change, theversion field is incremented. Consumers should remain backward compatible with the previous version wherever possible. Breaking version changes should be introduced as new event types rather than silent payload modifications.
Future Evolution Path
The event contracts are stable regardless of which transport layer carries them. Only the bus implementation changes across phases — theDomainEvent structure and the consumer handler signatures remain untouched.
Phase 01 — In-Process Event Bus
NestJS EventEmitter2 dispatches events synchronously within the application process. No external dependencies required.Phase 02 — Transactional Outbox Pattern
Events are written to an outbox table within the same database transaction as the aggregate change, then relayed to a broker by a background worker. Eliminates the risk of a committed aggregate state and a dropped event.