Skip to main content

Architecture Philosophy

Wolfix.Server is built as a modular monolith - a single deployable application composed of loosely coupled, highly cohesive modules. Each module represents a bounded context in the domain and follows Clean Architecture principles.

Core Principles

Bounded Contexts

Each module encapsulates a specific business domain with clear boundaries

Explicit Dependencies

Modules communicate through well-defined integration events, not direct references

Layered Design

Every module follows the same layer structure: Domain, Application, Infrastructure, Endpoints

Shared Kernel

Common abstractions, value objects, and base classes live in Shared modules

Module Structure

Each business module follows a consistent 4-layer architecture:
{ModuleName}.Domain/          # Pure business logic & entities
├── {Aggregate}Aggregate/
│   ├── {Entity}.cs
│   ├── Entities/
│   ├── ValueObjects/
│   └── Enums/
├── Interfaces/               # Repository contracts
├── Projections/              # Read models
└── Services/                 # Domain services

{ModuleName}.Application/     # Use cases & orchestration
├── Services/
├── Dto/
├── Mapping/
└── EventHandlers/            # Integration event handlers

{ModuleName}.Infrastructure/  # Data access & external concerns
├── Repositories/
├── Configurations/           # EF Core configs
└── Contexts/

{ModuleName}.Endpoints/       # HTTP API endpoints
└── Endpoints/

{ModuleName}.IntegrationEvents/  # Cross-module contracts

Available Modules

Business Modules

Authentication, authorization, and user account management. Handles registration, login, JWT tokens, and role management.
Product catalog, categories, variants, attributes, reviews, and discounts. Central to the e-commerce domain.
Order processing, delivery methods, payment tracking, and order lifecycle management.
Customer profiles, shopping carts, favorites, bonuses, and violation tracking.
Seller accounts, seller applications, categories, and seller-specific operations.
Blob storage management for photos and videos using Azure Blob Storage.
Support tickets, requests categorization (bugs, orders, general), and support agent management.
Administrative accounts and operations (Basic and Super admin types).

Inter-Module Communication

Modules communicate exclusively through Integration Events to maintain loose coupling.

EventBus Pattern

The EventBus class (located in Shared.IntegrationEvents) orchestrates event publishing and handling:
Shared.IntegrationEvents/EventBus.cs
public sealed class EventBus(IServiceScopeFactory serviceProvider)
{
    public async Task<VoidResult> PublishWithoutResultAsync<TEvent>(TEvent @event, CancellationToken ct) 
        where TEvent : IIntegrationEvent
    {
        await using var scope = serviceProvider.CreateAsyncScope();
        
        var handlers = scope.ServiceProvider
            .GetServices<IIntegrationEventHandler<TEvent>>()
            .ToList();

        foreach (var handler in handlers)
        {
            VoidResult result = await handler.HandleAsync(@event, ct);
            if (result.IsFailure) return result;
        }
        
        return VoidResult.Success();
    }

    public async Task<Result<TResult>> PublishWithSingleResultAsync<TEvent, TResult>(TEvent @event,
        CancellationToken ct) where TEvent : IIntegrationEvent
    {
        await using var scope = serviceProvider.CreateAsyncScope();
        var handler = scope.ServiceProvider
            .GetRequiredService<IIntegrationEventHandler<TEvent, TResult>>();
        return await handler.HandleAsync(@event, ct);
    }
}

Integration Event Examples

// Verify seller exists before adding product
public record CheckSellerExistsForProductAddition(Guid SellerId) : IIntegrationEvent;

// Handler in Identity.Application
public class CheckSellerExistsHandler : 
    IIntegrationEventHandler<CheckSellerExistsForProductAddition, bool>
{
    public async Task<Result<bool>> HandleAsync(
        CheckSellerExistsForProductAddition @event, 
        CancellationToken ct)
    {
        bool exists = await _sellerRepository.IsExistAsync(@event.SellerId, ct);
        return Result<bool>.Success(exists);
    }
}

Key Design Patterns

Result Pattern

All operations return Result<T> or VoidResult to handle errors functionally:
Shared.Domain/Models/Result.cs
public sealed class Result<TValue>
{
    public TValue? Value { get; }
    public string? ErrorMessage { get; }
    public bool IsSuccess => ErrorMessage == null;
    public bool IsFailure => !IsSuccess;
    public HttpStatusCode StatusCode { get; }

    public static Result<TValue> Success(TValue value, HttpStatusCode statusCode = HttpStatusCode.OK)
        => new(value, statusCode);

    public static Result<TValue> Failure(string errorMessage, HttpStatusCode statusCode = HttpStatusCode.BadRequest)
        => new(errorMessage, statusCode);
}

Aggregate Pattern

Domain entities are organized into aggregates with a single root entity that enforces invariants:
  • ProductAggregate: Product (root), ProductMedia, Review, Discount
  • OrderAggregate: Order (root), OrderItem
  • CustomerAggregate: Customer (root), CartItem, FavoriteItem
  • CategoryAggregate: Category (root), ProductAttribute, ProductVariant

Repository Pattern

Data access is abstracted through repository interfaces defined in the Domain layer:
public interface IProductRepository : IBaseRepository<Product>
{
    Task<Product?> GetByIdWithMediaAsync(Guid id, CancellationToken ct);
    Task<IReadOnlyCollection<ProductShortProjection>> GetByCategoryAsync(Guid categoryId, CancellationToken ct);
}

Benefits of This Architecture

Maintainability

Clear separation of concerns makes code easier to understand and modify

Testability

Pure domain logic with no infrastructure dependencies

Scalability

Modules can be extracted into microservices if needed

Team Autonomy

Teams can work on different modules with minimal conflicts

Next Steps

Explore individual module documentation to understand:
  • Domain entities and aggregates
  • Use cases and application services
  • Infrastructure implementations
  • API endpoints and contracts

Build docs developers (and LLMs) love