Wow Compiler
wow-compiler is a set of KSP processors. It converts bounded-context and aggregate annotations into machine-readable metadata and Kotlin constants so runtime, query, and interface modules consume the same model declarations.
It does not directly generate an OpenAPI document or HTTP routes. Runtime/build components such as wow-openapi and wow-webflux consume compiler metadata and assemble their own outputs.
Installation
plugins {
id("com.google.devtools.ksp")
}
dependencies {
ksp("me.ahoo.wow:wow-compiler")
}Align the compiler with the application's Wow, Kotlin, and KSP versions. See Existing Project for dependency and capability selection instead of copying a complete Gradle setup from this explanation.
Processors and outputs
Three SymbolProcessorProvider implementations are registered through ServiceLoader:
| Processor | Main input | Output | Consumers |
|---|---|---|---|
MetadataSymbolProcessor | @BoundedContext, @AggregateRoot, and resolved commands/events | META-INF/wow-metadata.json | MetadataSearcher, aggregate parsing, route/Schema/OpenAPI components |
AggregatesMetadataProcessor | A bounded context and aggregates in its package scope | AggregatesMetadata.kt in the same package | Type-safe aggregate metadata references |
QuerySymbolProcessor | Aggregate state types and nested properties | <StateType>Properties.kt | Query conditions, sorting, and field references |
These are build outputs. Do not edit or commit them. Change annotations/domain types and rerun KSP.
META-INF/wow-metadata.json
The resource records data by context name:
- context alias, description, and package scopes;
- aggregate name, aggregate type, and package scopes;
- optional static tenant ID and ID-generator name;
- command and event type-name sets.
wow-metadata.json does not store a state-aggregate type separately. The command-aggregate/state-aggregate pairing appears only in the later generated AggregatesMetadata.kt, as the two type parameters of aggregateMetadata<CommandAggregateType, StateAggregateType>().
MetadataSymbolProcessor writes it as an aggregating KSP output. At runtime, MetadataSearcher finds every resource with that name on the classpath and merges them. If a module's resource is absent, its aggregate/command/event contract is not reconstructed completely through reflection.
Inspect the application artifact, not only the source tree:
jar tf build/libs/<application>.jar | grep 'META-INF/wow-metadata.json'The final path and JAR name belong to the application build; placeholders are not a fixed command to copy verbatim.
AggregatesMetadata.kt
The generated object is placed in the bounded-context package and provides a typed value for each resolved aggregate:
object AggregatesMetadata {
val OrderAggregateMetadata = aggregateMetadata<Order, OrderState>()
}A single-class aggregate has the same command-aggregate/state-aggregate type; the composition pattern retains two different type parameters. This file is a convenient metadata reference, not a new public business API and not a replacement for wow-metadata.json.
Query-property constants
QuerySymbolProcessor traverses state properties and continues into nested types that are not Kotlin/Java simple types:
object OrderStateProperties {
const val ID = "id"
const val SHIPPING_ADDRESS = "shippingAddress"
const val SHIPPING_ADDRESS__CITY = "shippingAddress.city"
}Constant names use uppercase snake case, nested constant names use __, and values use dotted paths. A set of already-added navigation entries stops repeated traversal. Constants reduce handwritten spelling errors; they do not prove a field or index exists in the selected storage backend.
Compile-time and runtime failures
| Symptom | Check first |
|---|---|
No wow-metadata.json | Whether the domain module applies KSP, contains resolvable annotations, and packages the resource |
| One aggregate is absent | Package scope, aggregate annotation, module dependency, and final classpath |
| Property constants are stale | Clean and rerun KSP; confirm current generated directories are used |
| Runtime cannot parse aggregate metadata | Merged classpath resources against actual aggregate classes |
| OpenAPI/route is absent | Verify metadata first, then wow-openapi/wow-webflux capability, conditions, and runtime registration |
Do not repair a missing generated directory by hand-writing metadata. The root cause is normally that KSP did not run, a module is not depended on, or output was not packaged.
Verification
Compiler tests inspect all three output categories directly:
./gradlew :wow-compiler:test --tests "me.ahoo.wow.compiler.metadata.MetadataSymbolProcessorTest"
./gradlew :wow-compiler:test --tests "me.ahoo.wow.compiler.aggregate.metadata.AggregatesMetadataSymbolProcessorTest"
./gradlew :wow-compiler:test --tests "me.ahoo.wow.compiler.query.QuerySymbolProcessorTest"Application acceptance must additionally inspect the final JAR and actual OpenAPI/routes. Compiler tests do not prove that the application requested the corresponding runtime capability.
Source and related pages
MetadataSymbolProcessorAggregatesMetadataProcessorQuerySymbolProcessor- OpenAPI: how metadata enters interface contracts
- JSON Schema: schema-generation responsibility