Skip to content

A scalable, sample work for microservices architecture built with .NET, designed to support multi-tenant SaaS applications. This project demonstrates clean separation of services, tenant-aware request handling, secure communication, and cloud-ready deployment patterns.

License

Notifications You must be signed in to change notification settings

SkyThonk/MultitenancyMicroservices

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Multi-Tenant Microservices Architecture

A sample demonstration of my approach to building distributed systems with .NET. Features multi-tenant microservices architecture using Clean Architecture, DDD, and CQRS patterns. Includes event-driven communication via Wolverine and Kafka, tenant isolation, and orchestration with Aspireβ€”showcasing my ability to design scalable, maintainable enterprise systems.

.NET 9 PostgreSQL Redis Kafka License: MIT

πŸ—οΈ Architecture Overview

This project demonstrates a production-ready multi-tenant microservices architecture built with modern .NET practices. It showcases:

  • Clean Architecture with Domain-Driven Design (DDD)
  • CQRS Pattern using Wolverine for command/query separation
  • Event-Driven Communication with Wolverine and Apache Kafka
  • Multi-Tenancy with tenant isolation at database and application levels
  • Service Orchestration using .NET Aspire
  • Strongly Typed IDs and Value Objects for type safety
  • Repository Pattern with Unit of Work
  • Result Pattern for consistent error handling

πŸ“‹ Table of Contents

✨ Features

Multi-Tenancy

  • Complete tenant isolation
  • Tenant-specific credentials management
  • Subscription and usage tracking
  • Plan-based feature access control

User Management

  • JWT-based authentication
  • Role-based authorization
  • Secure password handling
  • User profile management

Event-Driven Architecture

  • Asynchronous messaging with Wolverine
  • Kafka integration for inter-service communication
  • Outbox pattern for reliable messaging
  • Event sourcing capabilities

Developer Experience

  • Hot reload support
  • Comprehensive logging
  • Redis caching layer
  • .NET Aspire dashboard for monitoring

πŸ› οΈ Technology Stack

Core Framework

  • .NET 9 - Latest .NET framework
  • ASP.NET Core - Web API framework
  • Entity Framework Core - ORM with PostgreSQL provider

Messaging & Events

  • Wolverine - CQRS and messaging framework
  • Apache Kafka - Distributed event streaming
  • MassTransit - Service bus abstraction

Data Storage

  • PostgreSQL - Primary database
  • Redis - Caching and distributed locking
  • Entity Framework Outbox - Reliable message delivery

Service Orchestration

  • .NET Aspire - Cloud-native application orchestration

Development Tools

  • Swagger/OpenAPI - API documentation
  • Serilog - Structured logging

πŸ“ Project Structure

MultitenancyMicroservices/
β”œβ”€β”€ AppHost/                          # .NET Aspire orchestration host
β”‚   β”œβ”€β”€ Program.cs                    # Service configuration and composition
β”‚   └── appsettings.json              # Centralized configuration
β”‚
β”œβ”€β”€ Common/                           # Shared kernel across all services
β”‚   β”œβ”€β”€ Common.Application/           # Shared application layer
β”‚   β”‚   β”œβ”€β”€ Authentication/           # JWT and auth abstractions
β”‚   β”‚   β”œβ”€β”€ Interfaces/               # Common interfaces (IUnitOfWork, etc.)
β”‚   β”‚   └── Result/                   # Result pattern implementation
β”‚   β”œβ”€β”€ Common.Domain/                # Shared domain layer
β”‚   β”‚   β”œβ”€β”€ Abstractions/             # Base entity, value objects
β”‚   β”‚   β”œβ”€β”€ Constants/                # Domain constants
β”‚   β”‚   └── GlobalUser/               # Global user context
β”‚   β”œβ”€β”€ Common.Infrastructure/        # Shared infrastructure
β”‚   └── Common.Presentation/          # Shared presentation logic
β”‚
β”œβ”€β”€ TenantService/                    # Tenant management microservice
β”‚   β”œβ”€β”€ TenantService.Api/            # REST API endpoints
β”‚   β”œβ”€β”€ TenantService.Application/    # CQRS handlers
β”‚   β”œβ”€β”€ TenantService.Contracts/      # DTOs and requests
β”‚   β”œβ”€β”€ TenantService.Domain/         # Domain entities and logic
β”‚   β”œβ”€β”€ TenantService.Persistence/    # Data access layer
β”‚   └── TenantService.Infrastructure/ # External service integrations
β”‚
└── UserManagement/                   # User management microservice
    β”œβ”€β”€ UserManagement.Api/           # REST API endpoints
    β”œβ”€β”€ UserManagement.Application/   # CQRS handlers
    β”œβ”€β”€ UserManagement.Contracts/     # DTOs and requests
    β”œβ”€β”€ UserManagement.Domain/        # Domain entities and logic
    β”œβ”€β”€ UserManagement.Persistence/   # Data access layer
    └── UserManagement.Infrastructure/ # External service integrations

Layer Responsibilities

  • Domain: Pure business logic, entities, value objects, domain events
  • Application: CQRS handlers, use case orchestration, repository interfaces
  • Contracts: Request/Response DTOs, shared between API and Application
  • Infrastructure: External service implementations (messaging, caching, etc.)
  • Persistence: EF Core DbContext, repositories, migrations
  • API: RESTful endpoints, controllers, Swagger configuration

🎯 Prerequisites

πŸš€ Getting Started

Installation

  1. Clone the repository

    git clone https://github.com/yourusername/MultitenancyMicroservices.git
    cd MultitenancyMicroservices
  2. Restore dependencies

    dotnet restore
  3. Configure connection strings

    Create service-specific appsettings.json files from the examples:

    UserManagement Service - Create UserManagement/UserManagement.Api/appsettings.json:

    {
      "ConnectionStrings": {
        "SQL": "Host=localhost;Port=5432;Database=usermanagement;Username=postgres;Password=your-password"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      }
    }

    TenantService - Create TenantService/TenantService.Api/appsettings.json:

    {
      "ConnectionStrings": {
        "SQL": "Host=localhost;Port=5432;Database=tenantservice;Username=postgres;Password=your-password"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      }
    }

    AppHost Configuration - Create AppHost/appsettings.json (not tracked by git):

    {
      "UserManagement": {
        "ConnectionStrings": {
          "SQL": "Host=localhost;Port=5432;Database=usermanagement;Username=postgres;Password=your-password"
        }
      },
      "TenantService": {
        "ConnectionStrings": {
          "SQL": "Host=localhost;Port=5432;Database=tenantservice;Username=postgres;Password=your-password"
        }
      },
      "JwtSettings": {
        "Secret": "your-secret-key-must-be-at-least-32-characters-long",
        "ExpiryMinutes": 60,
        "Issuer": "YourApp",
        "Audience": "YourApp"
      },
      "ConnectionStrings": {
        "redis": "localhost:6379,password=your-redis-password,ssl=false"
      },
      "Kafka": {
        "BootstrapServers": "localhost:9092",
        "SecurityProtocol": "Plaintext"
      }
    }
  4. Apply database migrations

    # User Management Service
    cd UserManagement/UserManagement.Api
    dotnet ef database update --project ../UserManagement.Persistence
    
    # Tenant Service
    cd ../../TenantService/TenantService.Api
    dotnet ef database update --project ../TenantService.Persistence
  5. Run the application via AppHost

    cd ../../AppHost
    dotnet run

    Or with hot reload:

    dotnet watch run

The AppHost will start both microservices and the Aspire dashboard:

Running Individual Services

For development, you can run services individually:

# User Management
cd UserManagement/UserManagement.Api
dotnet run

# Tenant Service
cd TenantService/TenantService.Api
dotnet run

βš™οΈ Configuration

The application uses a hierarchical configuration system:

  1. appsettings.json - Base configuration with placeholders (in source control)
  2. appsettings.Development.json - Development overrides
  3. appsettings.local.json - Local secrets (NOT in source control)

Configuration Structure

The AppHost/appsettings.json contains placeholder values for:

  • Database connection strings (PostgreSQL)
  • JWT settings
  • Redis connection
  • Kafka configuration

IMPORTANT: Never commit real credentials. Always use appsettings.local.json for local development.

Environment Variables (Alternative)

You can override configuration using environment variables:

# Windows PowerShell
$env:ConnectionStrings__SQL = "your-connection-string"
$env:JwtSettings__Secret = "your-jwt-secret"

# Linux/macOS
export ConnectionStrings__SQL="your-connection-string"
export JwtSettings__Secret="your-jwt-secret"

πŸ›οΈ Architecture Patterns

Domain-Driven Design (DDD)

Entities

  • Sealed classes with private constructors
  • Strongly typed IDs for type safety
  • Static factory methods for creation
public sealed class User : Entity<UserId>
{
    private User(UserId id, string name, string email) : base(id)
    {
        Name = name;
        Email = email;
    }

    public string Name { get; private set; }
    public string Email { get; private set; }

    public static User Create(string name, string email)
    {
        return new User(new UserId(Guid.NewGuid()), name, email);
    }
}

public record UserId(Guid Value)
{
    public static implicit operator Guid(UserId id) => id.Value;
    public static implicit operator UserId(Guid value) => new(value);
}

Value Objects

  • Implemented as records for immutability
  • Encapsulate domain logic

CQRS with Wolverine

Commands

public record CreateUserCommand(string Name, string Email, string Password);

public class CreateUserHandler
{
    private readonly IUserRepository _repository;
    private readonly IUnitOfWork _unitOfWork;

    public async Task<Result<UserResponse>> Handle(
        CreateUserCommand command, 
        CancellationToken ct)
    {
        var user = User.Create(command.Name, command.Email, command.Password);
        await _repository.AddAsync(user, ct);
        await _unitOfWork.SaveChangesAsync(ct);

        return Result<UserResponse>.Success(new UserResponse(user.Id));
    }
}

Queries

public record GetUserQuery(Guid UserId) : IQuery;

public class GetUserHandler
{
    public async Task<Result<UserResponse>> Handle(
        GetUserQuery query, 
        CancellationToken ct)
    {
        var user = await _repository.GetByIdAsync(query.UserId, ct);
        
        if (user is null)
            return Result<UserResponse>.Failure(new NotFoundError("User not found"));

        return Result<UserResponse>.Success(new UserResponse(user.Id));
    }
}

Result Pattern

Consistent error handling across all operations:

public class Result<T>
{
    public bool IsSuccess { get; }
    public T? Value { get; }
    public Error? Error { get; }

    public static Result<T> Success(T value) => new(true, value, null);
    public static Result<T> Failure(Error error) => new(false, default, error);
}

πŸ“š API Documentation

Once running, access Swagger documentation:

Example Endpoints

User Management

POST   /api/users              - Create new user
GET    /api/users/{id}         - Get user by ID
PUT    /api/users/{id}         - Update user
DELETE /api/users/{id}         - Soft delete user
POST   /api/auth/login         - Authenticate user
POST   /api/auth/refresh       - Refresh JWT token

Tenant Service

POST   /api/tenants            - Create new tenant
GET    /api/tenants/{id}       - Get tenant by ID
PUT    /api/tenants/{id}       - Update tenant
GET    /api/plans              - Get available plans
POST   /api/subscriptions      - Create subscription
GET    /api/usage              - Get usage statistics

πŸ”§ Development

Building the Solution

dotnet build

Running Tests

# Run all tests
dotnet test

# Run tests for specific service
cd UserManagement/UserManagement.Tests
dotnet test

Database Migrations

Each service manages its own database:

# Create migration
cd UserManagement/UserManagement.Api
dotnet ef migrations add MigrationName -p ../UserManagement.Persistence

# Update database
dotnet ef database update -p ../UserManagement.Persistence

Development Guidelines

Creating New APIs

  1. Define Request/Response in Contracts

    public record CreateUserRequest(
        [Required] string Name,
        [EmailAddress] string Email,
        [MinLength(6)] string Password
    );
  2. Create Controller Endpoint

    [ApiController]
    [Route("api/[controller]")]
    public class UserController : ControllerBase
    {
        [HttpPost]
        public async Task<IActionResult> CreateUser(
            [FromBody] CreateUserRequest request,
            CancellationToken ct)
        {
            var result = await _messageBus.InvokeAsync<Result<UserResponse>>(request, ct);
            return result.ToActionResult(this);
        }
    }
  3. Implement Handler in Application

    public class CreateUserHandler
    {
        public async Task<Result<UserResponse>> Handle(
            CreateUserRequest request, 
            CancellationToken ct)
        {
            // Implementation
        }
    }

πŸ”’ Security

CRITICAL: This repository has been sanitized for public sharing.

What's Been Done

  • βœ… All sensitive data replaced with placeholders
  • βœ… Git history cleaned (see remove-sensitive-data.ps1)
  • βœ… .gitignore updated to prevent future credential commits

For Developers

  • Never commit real credentials
  • Use appsettings.local.json for local development (already in .gitignore)
  • Use environment variables or secret management for production

Production Deployment

  • Use Azure Key Vault, AWS Secrets Manager, or similar
  • Enable encryption at rest and in transit
  • Rotate credentials regularly
  • Use managed identities when possible

πŸ› Troubleshooting

  • Port conflicts: Ensure ports 5001, 5002, 7001, 7002 are available
  • Database connection: Verify PostgreSQL is running and credentials are correct
  • Redis/Kafka: Check service availability and network access
  • Migrations: Ensure connection strings are set before running EF migrations

πŸ“ License

This project is licensed under the MIT License - see below for details.

MIT License

Copyright (c) 2025 Akash Jaiswal

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

πŸ‘€ Author

Your Name

πŸ™ Acknowledgments

  • .NET Aspire team for orchestration framework
  • Wolverine for CQRS implementation
  • The .NET community

Note: This is a demonstration project showcasing architecture patterns and best practices for building distributed systems with .NET. It represents my approach to solving complex software engineering challenges in enterprise environments.

About

A scalable, sample work for microservices architecture built with .NET, designed to support multi-tenant SaaS applications. This project demonstrates clean separation of services, tenant-aware request handling, secure communication, and cloud-ready deployment patterns.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published