İçeriğe Atla
Mustafa Erbay
Kariyer · 9 dk okuma · görüntülenme Read in English

Monolith vs Modular Monolith: Bir Indie Hacker'in Tercihi

Indie hacker olarak yazılım mimarisi seçimi: Monolith'in kolay başlangıcı ve Modular Monolith'in esnekliği arasındaki dengeyi kendi deneyimlerimle inceliyorum.

100%

Yeni bir fikirle yola çıkan, tek başına veya küçük bir ekiple bir ürün geliştirmeye çalışan bir “indie hacker” için mimari kararlar kritik önem taşır. Ben de yıllardır kendi yan ürünlerimi geliştirirken, veya büyük kurumsal projelerde çalışırken bu kararların ne kadar belirleyici olduğunu defalarca gördüm. Özellikle de Monolith ve Modular Monolith arasındaki seçim, projenin gelecekteki ölçeklenebilirliği, bakım maliyeti ve geliştirme hızı üzerinde doğrudan etki ediyor. Bu yazıda, bu iki yaklaşımı kendi gözümden, pratik faydaları ve zorlukları üzerinden değerlendireceğim ve bir indie hacker olarak benim genellikle hangi yolu tercih ettiğimi anlatacağım.

Bir projeye başlarken mimari seçimi, genellikle ürünün pazarla buluşma hızını ve ilk iterasyonlardaki çevikliği doğrudan etkiler. Monolith, başlangıçta cazip gelse de, zamanla getirebileceği yükleri iyi hesaplamak gerekiyor. Modular Monolith ise, mikroservislerin getirdiği karmaşıklığa bulaşmadan, daha düzenli ve sürdürülebilir bir yapı kurmanın bir yolunu sunuyor. Benim tecrübelerime göre, bu dengeyi doğru kurmak, özellikle sınırlı kaynaklarla çalışan bir indie hacker için hayati.

Monolith: Hızlı Başlangıç ve Gizli Maliyetler

Monolith mimarisi, tüm uygulama bileşenlerinin tek bir kod tabanında ve tek bir dağıtım birimi olarak bulunması anlamına gelir. Yani, bir web uygulaması düşünün; veritabanı erişimi, iş mantığı, kullanıcı arayüzü, API’ler hepsi aynı proje içinde, çoğu zaman aynı sunucu process’inde çalışır. Bu yaklaşım, özellikle hızlı prototipleme ve ilk ürün (MVP) aşamalarında inanılmaz bir hız sağlar. Birçok yan ürünümü başlangıçta Monolith olarak geliştirdim; çünkü hızlı bir şekilde fikirleri hayata geçirmem gerekiyordu. Tek bir repo, tek bir deploy süreci, tek bir test paketi… Her şey basit ve anlaşılır.

Ancak bu basitliğin zamanla ortaya çıkan gizli maliyetleri var. Bir üretim firmasının ERP’sini ilk geliştirmeye başladığımızda da benzer bir Monolith yapıdaydık. Başlangıçta küçük bir ekiple çok hızlı ilerledik. Ancak sistem büyüdükçe ve özellikler arttıkça, yeni bir özellik eklemek veya mevcut bir hatayı düzeltmek belirgin şekilde uzamaya başladı. Kod tabanı o kadar iç içe geçiyordu ki, bir yerdeki değişiklik başka bir yerde beklenmedik bir hataya yol açabiliyordu. Örneğin sipariş modülündeki küçük bir değişikliğin, faturalama modülünde hatalı indirim hesaplamasına neden olduğu durumlar yaşadık. Bu tür sorunları bulmak ve düzeltmek, kaynaklarımızı ciddi şekilde tüketiyordu.

Özellikle büyük ve kompleks iş süreçleri içeren bir yazılımda Monolith’in dezavantajları daha belirgin hale geliyor. PostgreSQL performansını optimize etmek için index stratejilerini incelerken veya N+1 sorgu problemlerini çözerken, Monolith’teki her yerin birbirine bağımlı olması, refactoring yapmayı veya yeni yaklaşımlar denemeyi zorlaştırıyor. Kendi yan ürünlerimde de aynı örüntüyü gördüm: basit bir Monolith ile başlayıp farklı domainler (örneğin filtreleme mantığı ile kullanıcı verisi yönetimi) büyüdükçe, her yeni özellik eklediğimde kod tabanının ne kadar kırılganlaştığını fark ettim. Bu, beni daha modüler bir yaklaşıma iten önemli bir deneyim oldu.

Modular Monolith: Bir Köprü Çözüm Olarak Gördüğüm

Modular Monolith, adından da anlaşılacağı gibi, Monolith yapısının avantajlarını korurken, içsel olarak modülerliği benimseyen bir yaklaşımdır. Uygulama hala tek bir kod tabanı ve tek bir dağıtım birimi olarak kalır, ancak kod tabanı içerisinde açıkça tanımlanmış, bağımsız modüllere ayrılmıştır. Her modül kendi domain’inden sorumludur ve diğer modüllerle yalnızca tanımlanmış arayüzler (API’ler veya olaylar) aracılığıyla iletişim kurar. Bu, tam bir mikroservis mimarisinin getirdiği operasyonel karmaşıklık (dağıtık transaction’lar, servis keşfi, dağıtık loglama vb.) olmadan, daha temiz ve yönetilebilir bir yapı oluşturmamızı sağlar.

Benim için Modular Monolith, özellikle orta ölçekli veya potansiyel olarak büyüyebilecek indie hacker projeleri için ideal bir köprü çözümdür. Bir müşteri projesinde, üretim planlama ve envanter yönetimini içeren bir sistemi geliştirirken bu yaklaşımı benimsedik. Başlangıçta hızlı bir şekilde ilerlemek istiyorduk ama gelecekte AI ile üretim planlama gibi kompleks modüllerin ekleneceğini biliyorduk. Bu yüzden, “Üretim”, “Envanter”, “Satış” gibi ana domain’leri ayrı Python paketleri veya dizinleri altında yapılandırdık. Her paket kendi PostgreSQL tablolarına sahipti ve diğer paketlerle sadece FastAPI endpoint’leri veya dahili in-memory event bus üzerinden haberleşiyordu.

# Örnek bir Modular Monolith proje yapısı
# my_project/
# ├── src/
# │   ├── __init__.py
# │   ├── main.py        # Ana uygulama girişi (FastAPI)
# │   ├── config.py
# │   ├── modules/
# │   │   ├── __init__.py
# │   │   ├── inventory/
# │   │   │   ├── __init__.py
# │   │   │   ├── models.py
# │   │   │   ├── services.py
# │   │   │   ├── api.py     # Inventory API endpoint'leri
# │   │   │   └── events.py
# │   │   ├── production/
# │   │   │   ├── __init__.py
# │   │   │   ├── models.py
# │   │   │   ├── services.py
# │   │   │   ├── api.py     # Production API endpoint'leri
# │   │   │   └── events.py
# │   │   └── sales/
# │   │       ├── __init__.py
# │   │       ├── models.py
# │   │       ├── services.py
# │   │       ├── api.py     # Sales API endpoint'leri
# │   │       └── events.py
# ├── tests/
# └── Dockerfile

Bu yapı sayesinde, “Envanter” modülündeki bir geliştirme, “Üretim” modülünü doğrudan etkilemeden yapılabilir hale geldi. Modüller arasındaki bağımlılıklar azaldığı için kod değişiklikleri daha güvenli hale geldi ve yeni özellik ekleme süresi kısaldı. Ayrıca, gelecekte “Üretim” modülünün aşırı yüklenmesi durumunda, onu bağımsız bir mikroservise dönüştürme seçeneğimiz de saklı kalıyordu, çünkü zaten kendi iç mantığı ve API’si ile izole edilmişti. Bu, özellikle indie hacker’lar için çok değerli; çünkü bugün ne kadar küçük olursa olsun, projenin yarın nereye evrileceğini asla bilemezsiniz.

Trade-off Analizi: Ne Kadar Karmaşıklık, Ne Kadar Esneklik?

Yazılım mimarisi seçimleri her zaman bir trade-off meselesidir. Monolith ve Modular Monolith arasında seçim yaparken de benzer bir durumla karşılaşıyoruz. Benim için anahtar soru şudur: “Şu anki ihtiyaçlarım ve kaynaklarım düşünüldüğünde, ne kadar karmaşıklığı göze alabilirim ve karşılığında ne kadar esneklik elde ederim?”

Özellik / Mimari Yaklaşım Monolith Modular Monolith
Geliştirme Hızı (Başlangıç) Çok yüksek, hızlı MVP Yüksek, Monolith’e yakın
Kod Tabanı Yönetimi Zorlaşır, bağımlılıklar artar Daha kolay, modüller arası net sınırlar
Dağıtım Karmaşıklığı Düşük, tek bir birim Düşük, tek bir birim (ancak potansiyel ayırma esnekliği)
Ölçeklenebilirlik Tüm uygulama birlikte ölçeklenir Modüller kısmen izole, daha esnek ölçeklenme potansiyeli
Teknoloji Esnekliği Düşük, tüm stack aynı Orta, modüller farklı teknolojilere ayrılabilir
Ekip Organizasyonu Küçük ekipler için uygun, büyüdükçe sorunlu Modül bazlı ekipler için uygun (daha iyi ayrışma)
Refactoring Kolaylığı Zor Daha kolay, modül içindeki değişiklikler izole kalır

Bir indie hacker olarak, genellikle başlangıçta geliştirme hızına öncelik veririm. Pazar testi yapmak, ürün-pazar uyumunu bulmak için hızlıca bir şeyler ortaya koymak önemlidir. Bu yüzden Monolith’in ilk cazibesine kapılmak kolaydır. Ancak deneyimlerim bana gösterdi ki, bu hızın uzun vadeli bir bedeli var. Tek bir codebase içinde farklı algoritmaları ve veri setlerini yönetmek, fonksiyon sayısı arttıkça ve yeni kullanıcı istekleri geldikçe hızla zorlaşır. Özellikle araya AI tabanlı bir tahminleme modülü gibi yeni bir parça eklemeye karar verdiğinizde, mevcut Monolith yapının ne kadar hantal kalabildiğini görürsünüz.

Bu noktada Modular Monolith yaklaşımının bir “kurtarıcı” olabileceğini düşünüyorum. Mevcut Monolith’i baştan parçalara ayırmak yerine, yeni eklenen her özelliği ayrı bir modül olarak tasarlamak daha sağlıklı. Örneğin bir “Tahminleme” modülünü kendi iç bağımlılıkları ve API’si ile izole edersiniz; bu modül ana uygulamanın bir parçası olarak deploy edilir ama kendi içinde bağımsız bir yaşam döngüsüne sahiptir. Bu sayede AI modelini güncellerken veya farklı bir AI provider’ı (Gemini Flash, Groq veya Cerebras gibi) denerken, ana uygulamanın diğer kısımlarını riske atmamış olursunuz. Bu, hem hız hem de geleceğe yönelik esneklik sağlayan bir dengedir.

Uygulama Detayları: Kod Seviyesinde Modulariteyi Sağlamak

Modular Monolith’in kağıt üzerinde harika görünmesi bir yana, asıl mesele bunu kod seviyesinde nasıl uygulayacağınızdır. Bir Monolith’i gerçekten modüler hale getirmek için disiplinli bir yaklaşım gerekir. Benim genelde kullandığım yöntem, projeyi domain’lere göre ayırmak ve bu domain’lerin kendi içinde kapalı birer paket gibi davranmasını sağlamaktır. Python dünyasında bu, ayrı dizinler, __init__.py dosyaları ve açıkça tanımlanmış import kuralları ile yapılabilir.

Örneğin, bir üretim ERP’sinde “Satış”, “Üretim” ve “Muhasebe” gibi ana modüllerim vardı. Her birini ana src/modules dizini altında ayrı birer alt dizin olarak oluşturdum.

# src/modules/sales/api.py
from fastapi import APIRouter
from src.modules.sales.services import create_order, get_order

router = APIRouter(prefix="/sales", tags=["Sales"])

@router.post("/orders/")
async def create_new_order(order_data: dict):
    # order_data'yı işleme al, validasyon yap
    order = create_order(order_data)
    # Dahili bir event fırlat (örneğin, order_created event)
    return {"message": "Order created", "order_id": order.id}

# src/modules/production/events.py
from src.core.event_bus import EventBus # Varsayımsal bir in-memory event bus

def handle_order_created(order_id: int):
    # Satış modülünden gelen "order_created" event'ini dinle
    # Üretim planlamayı tetikle
    print(f"Order {order_id} created. Initiating production plan.")
    # production_service.plan_production(order_id)

# Ana uygulama (main.py)
from fastapi import FastAPI
from src.modules.sales.api import router as sales_router
from src.modules.production.api import router as production_router
from src.modules.production.events import handle_order_created
from src.core.event_bus import EventBus

app = FastAPI()
app.include_router(sales_router)
app.include_router(production_router)

# Event listener'ları kaydet
EventBus.subscribe("order_created", handle_order_created)

Bu yapıda dikkat edilmesi gerekenler:

  1. Açık API’ler: Modüller arası iletişim, ya açıkça tanımlanmış FastAPI endpoint’leri üzerinden ya da bir EventBus (genellikle in-memory) üzerinden gerçekleşmeli.
  2. Tek Yönlü Bağımlılıklar (Tercihen): Mümkünse, modüller arasında tek yönlü bağımlılıklar kurmaya çalışırım. Örneğin, “Satış” modülü “Üretim” modülünü direkt çağırmaz, bir olay yayımlar ve “Üretim” modülü bu olayı dinler. Bu, bağımlılık döngülerini engeller.
  3. Veritabanı Şeması İzolasyonu: Her modülün kendi veritabanı tablolarına sahip olması (prefix ile veya ayrı şemalarla) idealdir. Böylece bir modülün veri şeması değişikliği diğerini daha az etkiler. Farklı iş birimlerinin verilerini ayrı şemalarda tutmak, bu izolasyonu sağlamanın etkili bir yoludur.
  4. Sıkı Kural Uygulama: Kod incelemelerinde modüller arası kurallara uyulup uyulmadığını kontrol etmek önemlidir. Gerekirse pre-commit hook’ları veya statik analiz araçları ile bu kurallar otomatikleştirilebilir.

Bu yaklaşım, bana hem geliştirme esnekliği sağlıyor hem de gelecekte performans sorunları yaşadığımda veya farklı bir teknolojiye ihtiyaç duyduğumda belirli bir modülü Monolith’ten ayırıp ayrı bir mikroservis olarak deploy etme olanağı tanıyor. Örneğin, AI ile üretim planlama modülü çok fazla kaynak tüketmeye başladığında, onu ayrı bir Kubernetes pod’unda çalıştırmak, diğer ERP bileşenlerini etkilemeden kolayca yapılabilir hale geliyor.

Operasyonel Bakış: Bare-metal ve Container Dünyasında Farklar

Mimari seçimlerimin operasyonel tarafa yansımaları, benim için her zaman öncelikli bir konu olmuştur. Saha tecrübemde, en iyi tasarlanmış sistemlerin bile operasyonel zorluklar yüzünden başarısız olduğunu çok gördüm. Monolith veya Modular Monolith fark etmeksizin, uygulama dağıtım ve yönetimi konusunda belli başlı tercihlerim var.

Ben bare-metal + container hibrit dağıtım yöntemini seven biriyim. Kendi VPS’lerimde veya müşteri projelerinde, genellikle Linux makineler üzerine Docker ve docker-compose kurarak ilerlerim. Monolith bir uygulama için Dockerfile yazmak ve onu docker-compose.yml ile çalıştırmak oldukça basittir. Tek bir servis, tek bir imaj.

# docker-compose.yml (Basit Monolith veya Modular Monolith için)
version: '3.8'
services:
  my_app:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: "postgresql://user:password@db:5432/mydatabase"
    depends_on:
      - db
    restart: unless-stopped
  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
volumes:
  postgres_data:

Bu docker-compose.yml dosyası, hem basit bir Monolith hem de içsel olarak modüler hale getirilmiş bir Modular Monolith için geçerlidir. Uygulama hala tek bir container içinde çalıştığı için operasyonel karmaşıklık artmaz. Nginx reverse proxy kurarak SSL sonlandırma ve statik dosya sunumu gibi işlemleri de my_app container’ından ayırabilirim.

Ancak, Modular Monolith’in gelecekteki operasyonel avantajları burada gizlidir. Eğer “AI ile üretim planlama” modülü gibi belirli bir modül, gelecekte aşırı kaynak tüketmeye başlarsa veya farklı bir runtime ortamına (örneğin GPU gerektiren bir ML altyapısı) ihtiyaç duyarsa, onu ana Monolith’ten ayırıp ayrı bir Docker container’ı olarak deploy etmek çok daha kolay olur. Zaten kendi iç API’leri ve bağımlılıkları izole edilmiş olduğu için, bu ayrıştırma süreci Monolith’e göre çok daha az sancılı geçer. Bu, self-hosted runner ekonomisi gibi alanlarda da benzer düşünce yapısını kullanırım; bir hizmeti ne zaman ayırıp ayrı bir VM’de çalıştırmalıyım veya ne zaman ana makine üzerinde tutmalıyım.

Benim journald loglarını takip ederken veya cgroup memory limit’lerini ayarlarken gördüğüm, tek bir process olarak çalışan uygulamanın yönetimi her zaman daha kolaydır. PostgreSQL connection pool tuning’i yaparken bile, tek bir uygulama instance’ı için ayarları yapmak, dağıtık mikroservisler için her servisin kendi pool’unu yönetmesinden daha az hataya açıktır. Bu yüzden, bir indie hacker olarak operasyonel basitliği her zaman ön planda tutarım.

Geleceğe Yönelik Düşünceler ve Benim Seçimim

Yazılım mimarisi, projenin ömrü boyunca sabit kalacak bir şey değildir; evrilir. Monolith ile başlayıp Modular Monolith’e geçiş yapmak veya Modular Monolith’ten belirli modülleri mikroservislere dönüştürmek, doğal bir büyüme sürecinin parçasıdır. Benim için bu evrimin tetikleyicileri genellikle şunlar olmuştur:

  • Ekip Büyüklüğü: Tek başıma veya 2-3 kişilik bir ekiple çalışırken Monolith veya Modular Monolith idealdir. Ancak ekip 10-15 kişiyi aştığında, farklı ekiplerin bağımsız çalışabilmesi için mikroservislere yönelmek daha mantıklı hale gelir.
  • Performans Engelleri: Belirli bir modülün (örneğin, benim AI tabanlı planlama modülüm gibi) kaynak tüketimi veya performans gereksinimleri, diğer uygulama bileşenlerini etkilemeye başladığında, onu ayırmak kaçınılmaz olur.
  • Teknoloji Çeşitliliği İhtiyacı: Tüm uygulamanın aynı teknoloji yığını ile gitmek zorunda kalması, yenilikleri veya farklı dil/framework’leri denemeyi zorlaştırır. Bir modülün Rust ile yazılması gerektiğinde, onu ayırmak iyi bir seçenektir.
  • İş Süreçlerinin Karmaşıklığı: Event-sourcing veya CQRS gibi gelişmiş mimari desenlerin sadece belirli domain’lerde anlamlı hale geldiği durumlarda, o domain’i izole etmek daha kolaydır.

Benim net pozisyonum, bir indie hacker projesi için Modular Monolith ile başlamaktır. Bu yaklaşım, bana Monolith’in hızlı geliştirme avantajını sunarken, aynı zamanda gelecekteki büyüme ve ölçeklenebilirlik ihtiyaçlarım için sağlam bir temel sağlıyor. Daha önce VPS migration sürecinde benzer bir trade-off yaşamıştım; basitlik mi, esneklik mi? Genellikle basitliği koruyarak esnekliği artırma yolunu seçerim. PostgreSQL index stratejileri veya Redis OOM eviction policy seçimleri gibi teknik detaylarda bile, başlangıçta genel bir çözümle başlayıp, performans regresyonları veya WAL bloat gibi somut sorunlar ortaya çıktığında daha spesifik ayarlamalara giderim. Bu pragmatik yaklaşım, kısıtlı zaman ve kaynaklarla çalışan bir indie hacker için en sürdürülebilir yoldur.

Bir sonraki yazımda, CI/CD reliability konusundaki tecrübelerimi ve blue-green veya canary deploy stratejilerini indie hacker projelerinde nasıl uyguladığımı anlatacağım.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

Sıkça Sorulanlar

Bu makale ile ilgili okurların sorduğu yaygın sorular.

Monolith mimarisini seçerken, hangi avantajlara dikkat etmeliyim?
Benim deneyimime göre, Monolith mimarisi özellikle hızlı prototipleme ve ilk ürün aşamalarında büyük avantajlar sunar. Tüm uygulama bileşenlerinin tek bir kod tabanında bulunması, hızlı bir şekilde fikirleri hayata geçirmemizi sağlar. Ayrıca, ilk iterasyonlardaki çevikliği doğrudan etkiler ve pazarla buluşma hızını artırır.
Modular Monolith ile mikroservis arasındaki seçim doingiliz?
Ben genellikle küçük ve orta ölçekli projeler için Modular Monolith'i tercih ediyorum. Mikroservislerin getirdiği karmaşıklığa bulaşmadan, daha düzenli ve sürdürülebilir bir yapı kurmanın bir yolu olduğunu düşünüyorum. Ancak, büyük ve çok katmanlı projelerde mikroservislerin esnekliği daha fazla önem kazanıyor.
Bir projede Monolith mimarisini kullanırken, hangi zorluklarla karşılaşabiliriz?
Benim deneyimime göre, Monolith mimarisi zamanla getirebileceği yükleri iyi hesaplamak gerekiyor. Tüm uygulama bileşenlerinin tek bir kod tabanında bulunması, bakım maliyetini artırabilir ve geliştirme hızını düşürebilir. Ayrıca, ölçeklenebilirlik konusunda da zorluklar çıkabilir.
Indie hacker olarak, bir projeye başlarken hangi araçları kullanmalıyım?
Ben genellikle hızlı ve esnek araçları tercih ediyorum. Örneğin, Node.js ve Express.js gibi teknolojiler, hızlı prototipleme ve ilk ürün aşamalarında büyük avantajlar sunar. Ayrıca, veritabanı olarak NoSQL çözümleri gibi esnek ve ölçeklenebilir seçenekleri değerlendirmeliyim.
ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Yeni yazılardan haberdar olun

Yeni içerikler ve teknik notlar e-postanıza gelsin.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar