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 
SharedveyaBuildingBlocksiç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ç toplamaScheduler→ 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:
RedisSchedulerKafkaEventBusSystemClockBufferedLoggerService
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 
BufferedLoggerServicetarafından background’da yapılır. - Hiçbir modül doğrudan 
ILoggerkullanmaz. 
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.





