MosaicMosaic

Think from the response up.

Composable backend orchestration for Kotlin

Start with what your endpoint returns. Build it from small, reusable Tiles. Let independent branches share the work.

Mosaic is a Kotlin library for application logic and response data. Bring your own HTTP framework.

Mosaic 0.6.0 · Apache 2.0
OrderPageTile.kt
val OrderPageTile =
singleTile {
val summaryTile = composeAsync(OrderSummaryTile)
val logisticsTile = composeAsync(LogisticsTile)
OrderPage(summaryTile.await(), logisticsTile.await())
}
An order response, composed from shared workOrderPageTile composes OrderSummaryTile and LogisticsTile. OrderSummaryTile composes CustomerTile, LineItemsTile, and OrderTile. Customer and line items also compose the same OrderTile. These are selected static dependencies; arrows point to work a Tile needs.OrderPageTileOrderSummaryTileLogisticsTileLineItemsTileCustomerTileOrderTileshared work
Three branches. One OrderTile execution per Mosaic.
Think from the response up, not the database down.Follow the composition

Start from what
the endpoint returns.

An order page needs a summary and shipping details. Each Tile builds its part of the response and asks for the dependencies it needs.

The endpoint stays small even when the graph behind it grows.

Tiles and composition
OrderSummaryTile.kt
val OrderSummaryTile =
singleTile {
val orderTile = composeAsync(OrderTile)
val customerTile = composeAsync(CustomerTile)
val lineItemsTile = composeAsync(LineItemsTile)
OrderSummary(orderTile.await(), customerTile.await(), lineItemsTile.await())
}

Independent work stays independent.

composeAsync starts each branch before you await its result. Use compose when the next step needs that value.

Compose concurrent work

Ask twice.
Execute once.

The page needs line items. So does the order total. Both branches reach the same Tile and share its in-flight work and result.

No branch needs to know who else is asking.

Shared work and cache identity
OrderTotalTile.kt
val OrderTotalTile =
singleTile {
val lineItemsTile = compose(LineItemsTile)
lineItemsTile.sumOf { it.price.amount * it.quantity }
}

Same Mosaic. Same Tile instance. A new Mosaic starts fresh.

Batch naturally.

A MultiTile puts keyed work behind one reusable dependency. Overlapping requests reuse equal keys; newly pending keys can join a batch before execution starts.

Ready work is never intentionally delayed. Exact batch boundaries depend on scheduling.

Work with MultiTile
ProductsByIdTile.kt
val ProductsByIdTile =
multiTile { keys ->
ProductService.getProducts(keys.toList())
}
First requestproduct-1
Next requestproduct-1 reusedproduct-2 fetched

Sequential calls in one Mosaic. Only the new key reaches the fetch block.

Choose bulk, per-key, or chunked fetching

Your composition
is architecture.

The optional analysis plugin turns Tile relationships into a graph. Keep the code as the model, instead of maintaining a second diagram by hand.

Terminal window
./gradlew mosaicGraph
Generate architecture reports
Generated order architecture: OrderPageTile composes summary and logistics. Three branches share OrderTile. Line items reach products and pricing; the selected graph also shows factory boundaries.
View full-size graph. Order architecture derived from real mosaicGraph output. Static dependencies, not runtime timing or batch boundaries.

See what
actually executed.

Add Mosaic execution spans to your OpenTelemetry traces. Dependency links reveal shared producers and batch callers; cache hits create no new spans.

val applicationCanvas = canvas {
tracing { openTelemetry }
}

Your application supplies OpenTelemetry, sampling, export, and shutdown.

Trace Tile execution

Test response logic
without booting the world.

Replace dependency Tiles with fixture values and leave the real response composition running. Test inputs, failures, and virtual-time delays in isolation.

val testMosaic = mosaicBuilder()
.withMockTile(OrderSummaryTile, summary)
.withMockTile(LogisticsTile, logistics)
.build()
testMosaic.assertEquals(
OrderPageTile, OrderPage(summary, logistics)
)

Inside runTest; summary and logistics are test fixture values.

Test your compositions

Measured cost.
Visible tradeoffs.

Mosaic handles orchestration you would otherwise write yourself. The published application benchmark compares it with equivalent optimized Kotlin doing the same downstream work.

For the service-backed aggregate graph at 800 RPS, Mosaic added about 115 µs of CPU/request. Both implementations had median HTTP latency of about 21.12 ms.

Read the evidence and limitations
Additional CPU per request
WorkloadMosaic − direct Kotlin
Light / batching14–21 µs
Aggregate graph48–115 µs
Sibling coalescing61–78 µs
CPU-heavy6–9 µs

Rounded paired medians across measured profiles and rates. Source revision 129b0c7; application results were not remeasured for 0.6.0. CPU cost is not HTTP latency.

Bring your HTTP framework.

The same order Tiles run behind Spring Boot, Ktor, and Micronaut.
Keep routing and HTTP concerns in your framework. Compose the response with Mosaic.

Runnable framework examples. No published framework adapter is required.

Run an example

Build the response.
Compose the rest.

Add Mosaic to your Kotlin project.
Your first Tile is a few lines away.

Get started