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ü:

MimariAvantajDezavantaj
MonolithBasit, tek deployKod karmaşası, domain çakışması
MicroservicesBağımsız ölçeklenebilirlikOperasyonel karmaşa
Modular MonolithTek deploy, bağımsız domainleren 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)

KatmanSorumlulukÖrnek
DomainSaf iş kuralları, entity, aggregateQuiz, Question, QuizScheduledEvent
ApplicationUse case’ler, handler’larScheduleQuizHandler, StartQuizHandler
InfrastructureVeri 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

ÖzellikAçıklama
DDD uyumluHer modül kendi bounded context’ine sahip
Bağımsız geliştirmeModüller birbirinden izole
Test edilebilirDomain/Application bağımsız
Modüler büyümeMikroservise geçiş kolay
PerformantTek deploy, minimal network hop
Observability-readyBufferedLogger, 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.

DurumModular MonolithMicroservice
DeploymentTek binaryHer modül ayrı container
CommunicationIn-processgRPC / HTTP / Message Bus
StateTek DB, farklı schemaAyrı DB per service
LoggingShared (BufferedLogger)Distributed logging

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


📘 12️⃣ Özet

KatmanİçerikScope
Core (Domain + Application)İş mantığı, use case’lerHer modül için ayrı
InfrastructureTeknik detaylarPaylaşılan
BuildingBlocksCross-cutting abstractionsPaylaşılan
Presentation (API)HTTP endpointsOrtak 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 *