Module map
Translately’s backend is a single Gradle (Kotlin DSL) build with 13 modules under :backend:*. Each module is a narrow library; the single runnable artefact is :backend:app, which wires them into a Quarkus application.
Webapp and future CLI / SDK packages live outside the Gradle build in webapp/, sdks/, cli/ and use their own tooling (pnpm, Vite, TypeScript).
Module graph
flowchart TD
app[":backend:app<br/>Quarkus runtime<br/>integration tests"]
api[":backend:api<br/>JAX-RS resources<br/>filters + mappers"]
service[":backend:service<br/>use-cases<br/>AuthService, OrgService, ..."]
data[":backend:data<br/>JPA entities<br/>Panache repos<br/>Flyway migrations"]
security[":backend:security<br/>Scope, RBAC<br/>JWT, password, crypto<br/>TenantContext"]
email[":backend:email<br/>Quarkus Mailer + Qute"]
jobs[":backend:jobs<br/>Quartz job defs"]
ai[":backend:ai<br/>AiTranslator port<br/>Claude / OpenAI adapters"]
mt[":backend:mt<br/>MachineTranslator port<br/>DeepL / Google / AWS"]
storage[":backend:storage<br/>S3 / MinIO adapter"]
webhooks[":backend:webhooks<br/>HMAC sender, retries"]
cdn[":backend:cdn<br/>content bundle builder"]
audit[":backend:audit<br/>append-only log"]
app --> api
app --> service
app --> data
app --> jobs
app --> ai
app --> mt
app --> storage
app --> email
app --> webhooks
app --> cdn
app --> audit
api --> service
api --> security
service --> data
service --> security
service --> email
service --> ai
service --> mt
service --> storage
service --> audit
service --> webhooks
data --> security
jobs --> service
(Render via GitHub / Pages Mermaid support. A PNG export will land under diagrams/ the first time someone edits this file in a content-rich PR.)
Ownership and rules
:backend:securityis a leaf library. It depends on no other:backend:*module. Keep it that way so every module — including:data— can use scopes, password hashing, crypto, and tenant context without circular dependencies. Enums that need to exist in both:data(as JPA entity state) and:security(as pure Kotlin) are duplicated — seeOrganizationRole(data) vsOrgRole(security) with a test that asserts the name round-trip.:backend:dataowns Flyway migrations and Panache entities. No service, filter, resource, or controller code lives here. Entities are plain data — they may carry@PrePersist/@PreUpdatefor timestamp housekeeping but no business logic.:backend:serviceowns use-case orchestration. A service method is the transactional boundary (@Transactional) and is the only layer allowed to emit audit events or send email.:backend:apitranslates HTTP to services and back. Filters here are:TenantRequestFilter→ authenticators →ScopeAuthorizationFilter. Exception mappers mapAuthException/InsufficientScopeExceptionto the uniform{error:{code,message,details?}}envelope.:backend:appis the only module withquarkus-resteasy-reactiveat runtime scope. It wires CDI producers (CryptoServiceProducer, etc.), hosts the Quarkus test profile, and runs integration tests against Testcontainers Postgres + Mailpit.
Why this shape
- Boot time matters. Smaller JARs → faster dev loop → faster CI. The hard rule that
:securityhas no heavy deps lets unit tests in that module avoid starting Quarkus at all. - Testability. Each leaf module is trivially unit-testable with MockK. Integration tests (
*IT) live only in:backend:appand bring the full Quarkus + Testcontainers environment up; there is no “almost full” middle tier. - Clean replacement. Adapters in
:ai,:mt,:storage,:webhooks,:cdnimplement a port interface defined in their own package. Adding a new provider is a new class in the same module — no other modules change.
See .kiro/steering/architecture.md for the authoritative steering rule.