Bosca / Developers

Architecture

How Bosca fits together

Contracts split from implementations, a platform built from independently-versioned modules, wiring written at compile time, and infrastructure you reach through interfaces — not products. The same shape, in every subsystem.

Core & implementation

Contracts in core, code beside them

Every domain is a pair. core-<name> holds the interfaces and models — pure contracts, nothing infrastructural. <name> holds the repositories, controllers, and services that implement them. Anything downstream depends only on the core, so subsystems talk to each other through service interfaces, never each other's internals.

  • core modules: interfaces and models, no infrastructure.
  • impl modules: repositories, services, controllers, migrations.
  • Cross-subsystem calls go through a service, never a repository.
content
core-content/        # contracts
  ContentService.kt   # interface
  Content.kt          # model
content/             # implementation
  ContentServiceImpl.kt
  ContentRepository.kt
  ContentController.kt

Modular & versioned

Composed from versioned modules

Bosca is a set of independently-versioned modules, published as artifacts. A running service is composed from the ones it needs. Each module carries its own repositories, services, and controllers, and their wiring is generated at build time — you add your feature as a module in the same shape, depending on the contracts you build against.

  • Independently-versioned modules, published as artifacts.
  • A service is composed from the modules it needs.
  • Depend on the core contracts, and add yours the same way.
build.gradle.kts
dependencies {
  implementation("io.bosca:core-content")
  implementation("io.bosca:core-workops")
}

Code generation

Wiring written at build time

Annotation processors read your repositories, services, controllers, and jobs and emit real Kotlin into the build output — the JDBC calls, the dependency graph, the controller dispatch, the route handlers. It's generated by the build and never checked in, so what runs is ordinary code with no runtime reflection.

  • Generators run at build time, from your annotations.
  • The generated code lives in the build output, never committed.
  • At run time the wiring is ordinary compiled code, not reflection.
build/generated/ksp/
TaskRepositoryImpl.kt   # JDBC
TaskControllerWiring.kt # GraphQL
RouteHandlers.kt        # REST
JobExecutors.kt         # jobs

Infrastructure

Products behind interfaces

Domain code never names a cache, a broker, or a queue product. It uses an interface; configuration decides the backend. Operational data lives in PostgreSQL, on a bounded pool that runs JDBC on virtual threads — and because job enqueueing is tied to the transaction, work is only queued once the data that triggered it has committed.

  • Cache, pub/sub, and the job queue each pick a backend by config.
  • PostgreSQL holds operational data behind a bounded pool.
  • Jobs wait for the transaction to commit before they enqueue.
interfaces · backends
CacheManagerRedis · NATS KV
PubSubServiceRedis · NATS
JobQueueNATS · Redis

One codebase, many roles

Composed into the role it plays

The same platform runs as more than one service — a GraphQL and REST API, a background runner for jobs and events, the analytics collector and processor, the Git server, the artifacts registry, and the Kubernetes controller. Each runs only the part of the platform its role needs, from the one shared codebase.

API serverGraphQL & REST
Background runnerjobs & events
Analytics collectorevent ingestion
Analytics processorevent processing
Git serverrepositories & CI
Artifacts registrypackage distribution
Kubernetes controllercluster operations

Keep exploring

More for developers