Bu eğitim bir geliştiriciyi src/cluster_erp/modules/ altındaki boş bir klasörden; sunucunun yüklediği, rota sunan, istemcilere metadata yayımlayan ve diğer modüllerle EventBus üzerinden konuşan bir modüle götürür. Çalışılmış örnek olarak küçük records modülünü kullanır. Web ekranlarını ayrıntılı ele almaz; onlar için metadata ile ekran ekleyin sayfasına bakın.
Ne kuracaksınız¶
Sonunda, Module sınıfı ModuleBase'den türeyen bir module.py içeren src/cluster_erp/modules/<module_name>/ paketine sahip olursunuz. Sunucu onu açılışta keşfeder, yaşam döngüsü yöntemlerini çağırır, rotalarını /api/v1/<module_name> altına bağlar ve metadata'sını /api/v1/_meta içine katar. Mimari testler geçer. Sözleşmenin arka planı modül yaşam döngüsü sayfasındadır.
Sıra şeması sunucunun bir modüle yaptığı çağrıları gösterir.
sequenceDiagram participant App as server/app.py lifespan participant Reg as ModuleRegistry participant Mod as Module App->>Reg: load_all(app, bus, database, enabled_modules) Reg->>Mod: initialize(database) Reg->>Mod: register_routes(app) Reg->>Mod: register_events(bus) App->>Mod: /api/v1/_meta için get_metadata() App->>Reg: shutdown_all() Reg->>Mod: shutdown()
Düzyazıyla: açılışta lifespan, etkin modüllerin listesiyle load_all çağırır (src/cluster_erp/server/app.py:140). Kayıt defteri her ad için cluster_erp.modules.<name>.module modülünü import eder, Module() oluşturur ve initialize, register_routes ve register_events yöntemlerini bu sırayla çağırır (src/cluster_erp/core/module_registry.py:39-52). Metadata rotası yüklenen her modülün get_metadata sonucunu effective_module_meta üzerinden okur (src/cluster_erp/server/app.py:1537-1538). Kapanışta kayıt defteri her modülde shutdown çağırır ve bir hatayı fırlatmaz, günlüğe yazar (src/cluster_erp/core/module_registry.py:74-80).
Başlamadan önce¶
- Yerel geliştirme kurulumu sayfasındaki gibi çalışan bir çalışma kopyası ve veritabanı.
- Python 3.12 veya üstü (
pyproject.toml:9). devekindeki geliştirme araçları: mypy, pytest ve Ruff (pyproject.toml:79-84).
Adım 1: Paketi oluşturun¶
Bir __init__.py ile src/cluster_erp/modules/<module_name>/ klasörünü oluşturun. Kayıt defteri cluster_erp.modules altındaki her paketi modül adayı sayar (src/cluster_erp/core/module_registry.py:26-30). Klasör adı, sınır testinin denetlediği modül adı olur (tests/test_architecture_boundaries.py:25-27).
Her alt modül için models.py, schemas.py, services.py ve routes.py içeren bir alt klasör tutun; src/cluster_erp/modules/records/deactivation_reasons/ buna örnektir. Her alt modül modelini import eden üst düzey bir models.py ekleyin. Alembic her modül için tam olarak bu dosyayı import eder (migrations/env.py:76-85); src/cluster_erp/modules/records/models.py bir örnektir.
Adım 2: Modelleri tanımlayın¶
cluster_erp.core.models içindeki BaseModel'den türetin. Böylece bir ULID birincil anahtar, kiracı sütunu, denetim sütunları, yumuşak silme sütunları ve version sayacı gelir (src/cluster_erp/core/models.py:17-78). Benzersizliği, DeactivationReason gibi, yalnız canlı satırlarda kısmi bir indeksle bildirin (src/cluster_erp/modules/records/deactivation_reasons/models.py:16-25). Para sütunları Numeric ve Decimal kullanır; bkz. para ve Decimal.
Adım 3: Göçü ekleyin¶
İş şeması yalnız Alembic geçmişinden gelir; bir modül tabloları asla kendisi oluşturmaz. migrations/versions/ altına bir revizyon ekleyin. Yöntem, adlandırma ve denetimler veritabanı göçleri sayfasındadır. CI tüm geçmişi boş bir veritabanına uygular ve tam olarak bir head ister (.github/workflows/ci.yml:113-117).
Adım 4: Şemaları, servisleri ve rotaları yazın¶
İş mantığını, örneğin DeactivationReasonService gibi, AsyncSession alan bir servis sınıfına koyun. Rotalar servisi oluşturur ve girdiyi Pydantic şemalarıyla doğrular. Her rotayı, sınır testinin başka bir modülden izin verdiği tek import olan require_permission ile koruyun (src/cluster_erp/modules/records/deactivation_reasons/routes.py:10, src/cluster_erp/modules/records/deactivation_reasons/routes.py:24-36, tests/test_architecture_boundaries.py:24).
<entity>.create, .read, .update ve .delete biçimindeki yetki kodları, modül metadata'sındaki her varlık için açılışta tohumlanır (src/cluster_erp/server/app.py:642-652, src/cluster_erp/modules/auth/permissions/services.py:44-54). Ayrıntılar roller ve izinler sayfasındadır.
Adım 5: module.py dosyasını yazın¶
Tam olarak Module adlı bir sınıfla module.py oluşturun. Bu sınıf yoksa kayıt defteri ModuleLoadError fırlatır (src/cluster_erp/core/module_registry.py:45-47). Soyut üyeleri uygulayın: name, version, description, initialize, register_routes, register_events, shutdown ve get_metadata (src/cluster_erp/core/module_base.py:24-74).
src/cluster_erp/modules/records/module.py:36-55 dosyasından:
async def initialize(self, database: Database) -> None:
import cluster_erp.modules.records.models # noqa: F401
def register_routes(self, app: FastAPI) -> None:
from cluster_erp.modules.records.deactivation_reasons.routes import (
router as reasons_router,
)
from cluster_erp.modules.records.lifecycle_routes import (
router as lifecycle_router,
)
from cluster_erp.modules.records.retention.routes import (
router as retention_router,
)
app.include_router(reasons_router, prefix=self.api_prefix)
app.include_router(lifecycle_router, prefix=self.api_prefix)
app.include_router(retention_router, prefix=self.api_prefix)
def register_events(self, bus: EventBus) -> None:
pass
api_prefix varsayılan olarak /api/v1/<name> olur (src/cluster_erp/core/module_base.py:36-38). Mimari test modülde en az bir rota dekoratörü ister (tests/test_architecture_boundaries.py:63-72).
Adım 6: Metadata'yı bildirin¶
get_metadata, modülün adını, simgesini, kenar çubuğunu ve varlıklarını taşıyan bir ModuleMeta döndürür (src/cluster_erp/core/metadata.py:719-740). records modülü bir varlık ve boş bir kenar çubuğu döndürür (src/cluster_erp/modules/records/module.py:60-76). Web uygulaması ve masaüstü istemci listelerini, formlarını ve kartlarını bu nesneden kurar; bkz. metadata modeli. tests/test_metadata_driven_rules.py keşfedilen her modülde get_metadata çağırır; bu yüzden yöntem veritabanı olmadan çalışmalıdır (tests/test_metadata_driven_rules.py:23-29).
Adım 7: Diğer modüllere bağlanın¶
Başka bir modülü import etmeyin. Bir şeye tepki vermek için register_events içinde bir Event'e abone olun; veri istemek için tam olarak bir modülün yanıtladığı bir Query gönderin (src/cluster_erp/core/event_bus.py:55-102). Yeni sözleşmeler dondurulmuş dataclass olarak src/cluster_erp/core/contracts.py dosyasına girer. products_and_services modülü iki kalıbı da gösterir: sorgu işleyicileri kaydeder ve bir olaya abone olur (src/cluster_erp/modules/products_and_services/module.py:131-147). Bir işleyicinin içinde, geçerli kiracının veritabanını okumak için request_database() kullanın (src/cluster_erp/core/module_base.py:40-59).
Adım 8: Modülü etkinleştirin¶
Lifespan yalnız enabled_modules içindeki adları yükler (src/cluster_erp/server/app.py:140). Varsayılan liste 28 ad içerir (src/cluster_erp/config/settings.py:539-568). Yeni adı oraya ekleyin veya listeyi ayarlar başvurusu sayfasında anlatıldığı gibi ortam üzerinden verin. Listede olup paketi olmayan bir ad uyarıyla atlanır (src/cluster_erp/core/module_registry.py:68-71).
Sonucunuzu denetleyin¶
CI'ın çalıştırdığı kapıları depo kökünden çalıştırın:
python -m ruff check src tests
python -m mypy src
python -m pytest -q --no-header -p no:cacheprovider
Kaynak: .github/workflows/ci.yml:51-52, .github/workflows/ci.yml:124. tests/test_architecture_boundaries.py:46-72 üzerinden çıkarılan beklenen sonuç: modül hiçbir ihlal listesinde görünmez ve module.py ya da API eksikliğiyle bildirilmez. Açılışta sunucu modülü "Loaded modules" satırında günlüğe yazar (src/cluster_erp/server/app.py:141-144).
Ne öğrendiniz¶
- Bir modül,
Modulesınıfı olan bir pakettir; bkz. modül yaşam döngüsü. - Modüller yalnız EventBus üzerinden konuşur; bkz. yalnız EventBus ile modüller arası iletişim.
- Ekranlar metadata'dan gelir; bkz. metadata güdümlü arayüz.
- Kuralları sınır testleri korur; bkz. mimari sınırlar.
Sonraki adımlar¶
İlgili sayfalar¶
92f8051 numaralı commit ile 2026-11-13 tarihinde doğrulandı.