14: Domain Design & Business Logic¶
This section focuses on designing the Domain Layer and Business Logic Layer for Search.Api and Checkin.Api. While Section 13 defined the overall Clean Architecture shape, this section goes deeper into the actual domain modeling, rules, validation, domain events, and how the APIs enforce business processes.
If Section 13 explained the structure, Section 14 explains the behaviour.
14.1 What Is the Domain Layer?¶
The Domain Layer contains:
• Entities
• Value Objects
• Aggregates
• Domain Services
• Domain Events
• Validation Rules
• Business invariants
It is pure logic:
✓ No EF Core
✓ No Redis
✓ No RabbitMQ
✓ No logging
✓ No external APIs
This layer represents the heart of your system: what your system is, not how it stores or exposes data.
14.2 Domain Design Principles¶
14.2.1 The Domain Is the Most Stable Layer¶
Business rules change slowly compared to infrastructure. Your domain model should outlive:
• Frameworks
• Databases
• UI layers
• Messaging systems
If PostgreSQL disappears tomorrow and you switch to MongoDB: Your Domain Layer should remain untouched.
14.2.2 Entities, Value Objects, Aggregates¶
Entities: Objects with identity (ID) that changes over time.
Examples: SearchRequest, SearchResult, CheckinRecord, Passenger
Value Objects: Immutable objects defined by value, not identity.
Examples: DateRange, Price, EmailAddress, Coordinates
Aggregates: Clusters of related entities with one entry point (Aggregate Root).
Examples: Checkin (root) → Passenger, TicketNumber, TimeWindow SearchResult (root) → Individual offers, metadata
Aggregates enforce rules like:
• No invalid checkin times
• All passengers must have valid documents
• Search results must have consistent pricing
14.2.3 Domain Services¶
Used only when unavoidable:
• Logic does NOT belong to a specific entity
• Logic spans multiple aggregates
• Complex validations involving multiple inputs
Example: Checkin rules involving airline restrictions + passenger age.
Domain services are pure logic, no infrastructure calls.
14.2.4 Domain Events¶
Events that represent something important happening:
CheckinCompleted
SearchPerformed
PassengerAdded
PriceChanged
Events can trigger:
✓ Cache invalidation
✓ RabbitMQ publishes (via Application Layer)
✓ Internal logic in the domain
Domain events live in the Domain Layer, but the handlers execute in the Application Layer.
14.3 Domain Models for Search.Api¶
14.3.1 Search Criteria (Value Object)¶
Example:
public record SearchCriteria(string Origin, string Destination, DateOnly Date);
Rules:
• Origin cannot equal Destination
• Date must be in the future
• Strings must be valid IATA codes
14.3.2 SearchResult (Aggregate Root)¶
public class SearchResult {
public Guid Id { get; private set; }
public DateTime CreatedAt { get; private set; }
public IReadOnlyList<FlightOffer> Offers => _offers;
}
Rules:
• All offers must match the original criteria
• Prices must be non-negative
• Results expire after a short window (for caching)
14.3.3 FlightOffer (Entity)¶
public class FlightOffer {
public decimal Price { get; private set; }
public string FareType { get; private set; }
public IEnumerable<Segment> Segments { get; private set; }
}
Rules:
• Price >= 0
• Segments must be contiguous in time
• No overlapping segments
14.4 Domain Models for Checkin.Api¶
14.4.1 Passenger (Entity)¶
public class Passenger {
public string FirstName { get; }
public string LastName { get; }
public DateOnly BirthDate { get; }
}
Rules:
• FirstName/LastName cannot be empty
• Passenger must have a valid age for the flight (Application Layer might check airline rules)
14.4.2 TicketNumber (Value Object)¶
public record TicketNumber(string Value) {
public TicketNumber {
if (!Regex.IsMatch(Value, "^[A-Z0-9]+$"))
throw new DomainException("Invalid ticket number format.");
}
}
14.4.3 CheckinRecord (Aggregate Root)¶
public class CheckinRecord {
public Guid Id { get; }
public Passenger Passenger { get; }
public TicketNumber TicketNumber { get; }
public DateTime CheckinTime { get; private set; }
public bool Completed { get; private set; }
}
Rules:
✔ Check-in cannot occur twice
✔ Time must be within check-in window
✔ Must generate domain event CheckinCompleted
14.5 Business Workflow (Application Layer)¶
The Application Layer coordinates the domain model.
Example: Search Workflow¶
1. Validate search criteria (Application Layer + Domain Rules)
2. Check Redis cache
3. Query PostgreSQL (fallback to remote integrators if needed)
4. Map results to domain model
5. Publish SearchPerformed domain event
6. Save search result if needed (PostgreSQL)
7. Return DTO to API
Example: Checkin Workflow¶
1. Receive check-in request DTO
2. Validate passenger details
3. Load flight rules (if needed)
4. Create CheckinRecord aggregate
5. Add CheckinCompleted domain event
6. Save to PostgreSQL
7. Publish RabbitMQ message
8. Return confirmation
14.6 Domain Validation Strategy¶
Validation belongs in:
✓ Domain Layer → Business rules, invariants
✓ Application Layer → Use-case logic, coordination
✗ Controllers → Should not contain logic
Use:
• Exceptions for domain rule violations
• FluentValidation in Application Layer for request DTOs
Example:
if (criteria.Date < DateOnly.FromDateTime(DateTime.UtcNow))
throw new DomainException("Search date must be in the future.");
Domain exceptions bubble to the Application Layer and return:
400 Bad Request
14.7 Domain Events → Outbox Pattern (Optional Future)¶
If you want guaranteed event delivery, you can add an Outbox Table:
• Save event in PostgreSQL transaction
• Background worker reads outbox
• Publishes to RabbitMQ
• Marks outbox row as processed
This prevents:
✓ Lost events
✓ Duplicate messages
✓ Inconsistent state between database & queue
14.8 Summary of Section 14¶
You now have:
✔ Domain Models for Search & Checkin
✔ Clear Entity/Value Object/Aggregate definitions
✔ Domain Events
✔ Application workflow structure
✔ Validation strategy
✔ Domain-first business design
This ensures that LocalCloudLab grows as a real scalable platform, not a pile of controllers glued together.
Next section will continue automatically: Section 15 – Envoy Gateway Advanced Routing & Traffic Management