Modüler Monolith Mimarisi: Clean, Bounded, Scalable

Modüler Monolith Mimarisi: Clean, Bounded, Scalable

Hadi biraz kafa yoralım. 🧠
Bu mimari doküman, mükemmel Clean Architecture prensiplerine sahip bir modüler monolith için sağlam bir temel oluşturuyor.
Şimdi bunu rehber niteliğinde, herkesin anlayabileceği ama derinlemesine teknik bir blog yazısına dönüştürelim.

Aşağıdaki yazı:

  • 👷‍♂️ Gerçek bir modüler monolith nasıl inşa edilir?
  • 🧩 Her modül kendi “mini-core”una nasıl sahip olur?
  • 🏗️ Katmanlar nasıl ayrılır, ama sistem tek bir bütün olarak nasıl kalır?
  • ⚙️ BufferedLogger, Scheduler, Redis, Kafka gibi altyapılar modüler düzende nasıl yer alır?

Tüm bu soruların cevabını verir.
Hazırsan başlayalım 👇


🧱 Modüler Monolith Mimarisi: Clean, Bounded, Scalable

“Monolith ölmedi. Sadece modülerleşti.”
— Kamil Grzybek, Modular Monoliths with DDD


🎯 1️⃣ Neden Modüler Monolith?

Birçok ekip Microservice’lere geçmeden önce Clean Architecture + Modüler Monolith kombinasyonunu tercih ediyor.
Çünkü:

Mimari Avantaj Dezavantaj
Monolith Basit, tek deploy Kod karmaşası, domain çakışması
Microservices Bağımsız ölçeklenebilirlik Operasyonel karmaşa
Modular Monolith Tek deploy, bağımsız domainler ⚡ en iyi denge noktası

🧩 2️⃣ Temel Felsefe

“Her modül kendi küçük Clean Architecture Core’una sahiptir.”

Yani:

  • Her modülün Domain ve Application katmanı kendi içindedir.
  • Infrastructure katmanı ortaktır ama her modülün Interface’i üzerinden erişir.
  • Presentation (API) katmanı modülleri sadece Application arabirimlerinden çağırır.
  • Cross-cutting concerns (auth, logging, caching) gibi bileşenler Shared veya BuildingBlocks içinde yaşar.

🧠 3️⃣ Genel Yapı

src/
├── BuildingBlocks/
│   ├── Shared/
│   │   ├── Abstractions/
│   │   ├── Logging/
│   │   ├── Messaging/
│   │   └── Time/
│   └── Infrastructure/
│       ├── Outbox/
│       ├── EventBus/
│       ├── Redis/
│       └── Kafka/
│
├── Modules/
│   ├── Quiz/
│   │   ├── Quiz.Domain/
│   │   ├── Quiz.Application/
│   │   └── Quiz.Infrastructure/
│   │
│   ├── Scheduler/
│   │   ├── Scheduler.Domain/
│   │   ├── Scheduler.Application/
│   │   └── Scheduler.Infrastructure/
│   │
│   ├── Logging/
│   │   ├── Logging.Domain/
│   │   ├── Logging.Application/
│   │   └── Logging.Infrastructure/
│
└── Api/
    ├── Program.cs
    ├── ModulesRegistration.cs
    └── Controllers/

🧭 Her modül kendi bounded context’ine sahip:

  • Quiz → quiz oluşturma, başlatma, sonuç toplama
  • Scheduler → Redis/Kafka zamanlayıcı
  • Logging → BufferedLogger servisleri

🧩 4️⃣ Katmanların Rolü (Her Modül İçin)

Katman Sorumluluk Örnek
Domain Saf iş kuralları, entity, aggregate Quiz, Question, QuizScheduledEvent
Application Use case’ler, handler’lar ScheduleQuizHandler, StartQuizHandler
Infrastructure Veri erişimi, dış servis bağlantıları RedisScheduler, EventStoreQuizRepository

Domain (örnek)

// Modules/Quiz/Quiz.Domain/Entities/Quiz.cs
public class Quiz : AggregateRoot
{
    public Guid Id { get; private set; }
    public string Title { get; private set; }
    private readonly List<Question> _questions = new();

    public void Schedule(DateTime scheduledAtUtc, IClock clock)
    {
        if (scheduledAtUtc < clock.UtcNow)
            throw new InvalidOperationException("Cannot schedule quiz in the past.");

        AddDomainEvent(new QuizScheduledEvent(Id, scheduledAtUtc));
    }
}

Application (örnek)

// Modules/Quiz/Quiz.Application/UseCases/ScheduleQuiz/ScheduleQuizHandler.cs
public class ScheduleQuizHandler : ICommandHandler<ScheduleQuizCommand>
{
    private readonly IQuizRepository _quizRepository;
    private readonly IScheduler _scheduler;
    private readonly IBufferedLogger _logger;

    public async Task Handle(ScheduleQuizCommand command, CancellationToken ct)
    {
        var quiz = await _quizRepository.GetAsync(command.QuizId);
        quiz.Schedule(command.ScheduledAtUtc, SystemClock.Instance);

        await _scheduler.ScheduleAsync(command.QuizId, command.ScheduledAtUtc);
        await _quizRepository.SaveAsync(quiz);

        _logger.Enqueue(new LogEvent($"Quiz {command.QuizId} scheduled at {command.ScheduledAtUtc}"));
    }
}

Infrastructure (örnek)

// Modules/Quiz/Quiz.Infrastructure/Repositories/QuizRepository.cs
public class QuizRepository : IQuizRepository
{
    private readonly EventStoreContext _context;
    public QuizRepository(EventStoreContext context) => _context = context;

    public Task<Quiz> GetAsync(Guid id) => _context.Aggregates.FindAsync<Quiz>(id);
    public Task SaveAsync(Quiz quiz) => _context.SaveAsync(quiz);
}

🧱 5️⃣ Bağımlılık Yönetimi

🎯 “Katmanlar bağımlı değil, abstraction’lara bağımlı olmalı.”

Örnek referans yönü (projeler arası):

Quiz.Application  →  Quiz.Domain
Quiz.Infrastructure  →  Quiz.Application (interfaces)
Api  →  Quiz.Application (commands)

Infrastructure, Application’da tanımlanmış arabirimleri uygular:

// Application katmanında
public interface IQuizRepository
{
    Task<Quiz> GetAsync(Guid id);
    Task SaveAsync(Quiz quiz);
}

Infrastructure katmanında implement edilir:

public class QuizRepository : IQuizRepository { ... }

🧩 6️⃣ Paylaşılan Katmanlar

BuildingBlocks/Shared

  • IBufferedLogger, IClock, IEventBus, IScheduler
  • Ortak Result<T>, PaginatedList<T> gibi generic yapılar

BuildingBlocks/Infrastructure

  • Gerçek implementasyonlar:

    • RedisScheduler
    • KafkaEventBus
    • SystemClock
    • BufferedLoggerService

Her modül bu BuildingBlocks’taki abstraction’ları kullanır.


⚙️ 7️⃣ Modül Kaydı (Composition Root)

Tüm modüller API projesi tarafından runtime’da yüklenir.
Startup’ta modüller register edilir:

// Api/ModulesRegistration.cs
public static class ModulesRegistration
{
    public static IServiceCollection AddModules(this IServiceCollection services, IConfiguration config)
    {
        services.AddQuizModule(config);
        services.AddSchedulerModule(config);
        services.AddLoggingModule(config);
        return services;
    }
}

Her modül kendi bağımlılıklarını tanımlar:

// Modules/Quiz/Quiz.Infrastructure/DependencyInjection.cs
public static class QuizModule
{
    public static IServiceCollection AddQuizModule(this IServiceCollection services, IConfiguration config)
    {
        services.AddScoped<IQuizRepository, QuizRepository>();
        services.AddScoped<IQuizService, QuizService>();
        return services;
    }
}

🧠 8️⃣ Database & Transaction Yönetimi

“Tek database, ama her modül kendi şemasında yaşar.”

Önerilen yapı:

  • Tek SQL Server veya PostgreSQL
  • Her modül kendi şeması (quiz, scheduler, logging)
  • Transaction’lar Application servis seviyesinde yönetilir.
  • Outbox pattern kullanılır (Event Store ile tutarlılık için).

🧩 9️⃣ Cross-cutting Concerns (Logging, Caching, Auth)

BufferedLogger (örnek entegrasyon)

  • IBufferedLogger.Enqueue() Application seviyesinde çağrılır.
  • Gerçek log işlemi BufferedLoggerService tarafından background’da yapılır.
  • Hiçbir modül doğrudan ILogger kullanmaz.

Scheduler (örnek)

  • IScheduler.ScheduleAsync() Application katmanında çağrılır.
  • Redis veya Kafka implementasyonu sadece Infrastructure’da.

🚀 10️⃣ Modüler Monolith + Clean Architecture = DDD için Mükemmel Zemin

Özellik Açıklama
DDD uyumlu Her modül kendi bounded context’ine sahip
Bağımsız geliştirme Modüller birbirinden izole
Test edilebilir Domain/Application bağımsız
Modüler büyüme Mikroservise geçiş kolay
Performant Tek deploy, minimal network hop
Observability-ready BufferedLogger, Prometheus, OTEL entegre

🧩 11️⃣ Geleceğe Hazırlık: Microservice Geçişi

Modular Monolith → Microservice geçişi sadece deployment sınırı değiştirir, domain sınırı değil.

Durum Modular Monolith Microservice
Deployment Tek binary Her modül ayrı container
Communication In-process gRPC / HTTP / Message Bus
State Tek DB, farklı schema Ayrı DB per service
Logging Shared (BufferedLogger) Distributed logging

Bu nedenle her modül, potansiyel olarak kendi microservice’ine dönüşebilir.


📘 12️⃣ Özet

Katman İçerik Scope
Core (Domain + Application) İş mantığı, use case’ler Her modül için ayrı
Infrastructure Teknik detaylar Paylaşılan
BuildingBlocks Cross-cutting abstractions Paylaşılan
Presentation (API) HTTP endpoints Ortak giriş noktası

🧭 13️⃣ Sonuç

Modüler Monolith:

  • Clean Architecture’ın bağımlılık kurallarını uygular,
  • Her modülü bir bounded context olarak ele alır,
  • Geliştirme hızını artırır,
  • Gelecekte Microservices dönüşümünü sorunsuz hale getirir.

Ve bu tarz projelerde senin BufferedLogger mimarin, Event Sourcing + CQRS + Redis Scheduler yapısıyla birleştiğinde,
aslında modern bir Cloud-Native Modular Monolith elde ediyorsun —
yani tek binary ama microservice mimarisi kadar güçlü bir sistem.


admin

Leave a Reply

Your email address will not be published. Required fields are marked *