Skip to content

Feature catalogue

This is the everything-at-a-glance page: every construct Koine ships — the full tactical and strategic toolkit plus the developer tooling — with the short .koi syntax for it, the C# (or Markdown) it emits, and a pointer into the canonical pizzeria demo (which compiles the templates/pizzeria template). Tables are grouped by capability. Each family links to its reference page for the full story.

The core DDD vocabulary: types, fields, invariants, and the expression language. See value objects, entities and identity, aggregates, invariants, and expressions.

Construct.koi syntax (short)EmitsDemo location
Value objectvalue Price { amount: Decimal }sealed record with get-only props, validating ctor, value equalityMoney, Topping, Address, Coupon, Discount
Entity + identity Product identified by ProductCode { … }sealed class with identity-only equality + a generated id value objectPizza, Order, KitchenTicket, Delivery, Charge
Aggregate rootaggregate Order root Order { entity Order … }nested types in the context namespace; root implements IAggregateRoot; an I<Root>Repository is emittedOrder, Catalog (root Pizza), Dispatch (root Delivery), Billing (root Charge), Books (root LedgerEntry), Kitchen.Line.KitchenTicket
Typed fieldname: Typea typed property + ctor parameterevery type
Defaulted fieldstatus: OrderStatus = Drafta ctor parameter with a default valueOrder.status, KitchenTicket.stage
Derived fieldlineTotal: Money = price * quantitya get-only computed property (not in the ctor)OrderLine.lineTotal, Order.total
Range invariantinvariant amount >= 0 "…"a ctor guard that throws DomainInvariantViolationExceptionMoney.amount >= 0, OrderLine.quantity >= 1
Regex invariantinvariant code matches /…/ "…"a Regex.IsMatch guardCoupon.code, Address.postalCode
Conditional invariantinvariant status == Draft when lines.isEmptyif (cond && !body) throwOrder draft rule
Conditional expressionif cond then a else ba C# ternaryOrderLine.payable
String opscode.trim.upper, a + b.Trim(), .ToUpperInvariant(), string concatCoupon.normalized, Address.formatted
Collection ops + lambdaslines.sum(l => l.lineTotal), .count, .all, .distinctByLINQ (.Sum, .Count, .All, .DistinctBy); pulls using System.Linq;Order.total, Order invariants
Multiple contexts → namespacescontext Catalog { … }one C# namespace + folder per contextall six contexts

See value objects and contexts and types.

Construct.koi syntax (short)EmitsDemo location
Optional fielddescription: String?a nullable property; supports ?? and .isPresentPizza.description/kcal, KitchenTicket.startedAt
Settags: Set<String>IReadOnlySet<T> (de-duplicated in the ctor)Pizza.toppings
Doc comment/// summary texta C# XML <summary> on the member/typeordering.koi, menu.koi, payment.koi
Glossarykoine build … --glossary pizzeria.mda Markdown glossary grouped by context (each heading shows its version) then typethe --glossary flag

See commands, events & state.

Construct.koi syntax (short)EmitsDemo location
Commandcommand submit() { requires …; status -> Placed; emit … }a mutating method that checks requires, applies field -> value transitions, re-checks invariantsOrder.place/cancel, Delivery.pickUp/depart/complete, Charge.capture/refund
Command returning a valuecommand cancel(): OrderId { …; result id }a typed method (public <T> Name(…)) that returns the result expression as its terminal statement (the create-and-return-id idiom)
Domain eventevent OrderSubmitted { … } + emit OrderSubmitted(…)a record recorded into the root’s DomainEvents collectionOrderOpened, OrderPlacedInternally, DeliveryScheduled, ChargeAuthorized
State machinestates { Draft -> Placed; … }runtime-checked legal transitions; illegal transition throwsOrder, Delivery, Charge, KitchenTicket lifecycles

The aggregate’s only public construction path. See factories.

Construct.koi syntax (short)EmitsDemo location
Named factorycreate open(customer: CustomerId, …) { … }public static <Entity> Open(…): generate id, check requires, construct with named ctor args, emit, returnOrder.open, Delivery.schedule, Charge.authorize
Field inittotal -> lines.sum(l => l.price)a named ctor argument total: <expr>factory bodies
Auto-bindcreate open(customer: CustomerId, …) (param name = field)binds the matching field without an explicit ->Order.open
Creation eventemit OrderOpened(orderId: id, …)records the event into DomainEvents after constructionfactory bodies
Factory-only construction(presence of any create)the all-args constructor becomes privateevery aggregate with a factory

See enums and value objects.

Construct.koi syntax (short)EmitsDemo location
Smart enumenum OrderStatus { Draft, Placed, Shipped }sealed class with static instances, Name/Value, All, FromName/FromValue, value equality, ==/!=every enum
Enum with associated dataenum Currency(symbol: String, decimals: Int) { EUR("€", 2) }each signature field becomes a get-only PascalCase propertyCurrency(symbol, decimals)
Quantityquantity Weight { amount: Decimal unit: MassUnit }a value object with unit-checked +/- (throws on mixed units) and scalar *// that preserve the unitPortion
Range<T>window: Range<Instant>the runtime Koine/Runtime/Range.cs (Contains, Overlaps, start≤end guard); element must be Int, Decimal, or InstantHappyHour.window

See specs, services & policies.

Construct.koi syntax (short)EmitsDemo location
Specificationspec IsVip on Customer = …an extension-method predicate bool IsVip(this Customer x) in <Context>Specifications.cs; call as customer.IsVip(); reusable in invariantsPromotions.IsFreeOrder (on Discount)
Domain service (pure)service LoyaltyService { operation discountRate(…): Decimal = … }a sealed class with one expression-bodied method per operationPromotions.DiscountService
Domain service (seam)service Calc { operation run(a: Int): Int }an abstract class with abstract method seams(any bodyless operation)
Policypolicy PostToLedger when ChargeCaptured then Books.record(…)IPostToLedgerPolicy + an abstract PostToLedgerPolicy seam (the reaction is a doc sketch, not executed code)Payment.PostToLedger

See repositories & concurrency and entities and identity.

Construct.koi syntax (short)EmitsDemo location
Guid identity (default)identified by OrderIda Guid-backed id value object with a New() generatormost aggregates
Natural keyidentified by ProductCode as natural(String)a String/Int-backed id, value equality, no New(); blanks rejectedPizza (PizzaCode)
Sequence identityidentified by InvoiceNo as sequencea long-backed id, no New() (the store assigns it)sequence ids
Repository interface(any aggregate root)I<Root>Repository with GetByIdAsync/AddAsync/UpdateAsync/RemoveAsyncevery aggregate
Repository operations + findersrepository { operations: add, getById find byCustomer(…): List<Order> }tunes the mutating set; find → async …Async (list → IReadOnlyList<>, single → Root?)Ordering: byCustomer, mostRecent
Optimistic concurrencyaggregate Order root Order versioned { … }a get-only Version; Koine.Runtime.ConcurrencyConflictException on stale writesOrder

See application layer & CQRS and the application-layer tutorial.

Construct.koi syntax (short)EmitsDemo location
Unit of Work(≥1 aggregate in a context)<Context>/IUnitOfWork.cs with one I<Root>Repository property per aggregate (pluralized) + SaveChangesAsyncPayment.IUnitOfWork (Billing + Books), Menu, Ordering
Application serviceservice OrderingService { usecase PlaceOrder(…): OrderId }IOrderingService with one async method per use case (Task/Task<T>; List<T> params → IReadOnlyList<T>)Ordering.IOrderingService
Read model + projectionreadmodel OrderSummary from Order { id customer lineCount: Int = lines.count }a sealed record + a static ToOrderSummary(this Order src) projection mapperMenu.MenuItem, Ordering.OrderSummary
Query objectquery OrdersByStatus(status: OrderStatus): List<OrderSummary>a query DTO record + the shared Koine.Runtime.IQueryHandler<TQuery, TResult>PizzasBySize, PizzaByCode, OrdersByStatus

See multi-file, imports & modules and the multiple contexts tutorial.

Construct.koi syntax (short)EmitsDemo location
Directory compilationkoine build templates/pizzeria/ …every *.koi under the directory merges into one modelthe whole templates/pizzeria folder
Named importimport Menu.{ Topping }a precise using Menu;; names usable unqualifiedKitchen imports Menu.{ Topping }
Wildcard importimport Menu.*resolves all exported names from the context(companion form)
Qualified referenceaddress: Menu.Toppinga fully-qualified C# type, no using addedcross-context refs
Modulemodule Line { … }a <Context>.<Module> sub-namespace + sub-folderKitchen.Line

See context maps & integration events and the multiple contexts tutorial. The map is enforced and drives emission.

Construct.koi syntax (short)EmitsDemo location
Context mapcontextmap { Menu -> Kitchen : conformist }no type by itself; validates and permits cross-context referencescontext-map.koi (8 relationships)
Relation rolespartnership, shared-kernel, customer-supplier, conformist, anti-corruption-layer, open-host, published-languageeach role gates references/subscriptions differentlythe map relations
Shared kernelMenu <-> Ordering : shared-kernel { Currency }the shared type emitted once into Menu__Ordering/Kernel/; partners get a precise usingCurrency
Anti-corruption layerGateway -> Payment : anti-corruption-layer + acl { Gateway.GatewayResult -> Payment.PaymentReceipt }a translator interface IGatewayToPaymentTranslator in the downstream contextGateway -> Payment
Integration eventintegration event OrderPlaced { … }a sealed record : IIntegrationEvent; the marker is emitted onceOrdering.OrderPlaced, Kitchen.TicketReady
Publishpublishes OrderPlacedmarks the event as a published surface; authorizes subscribersOrdering, Kitchen
Subscribesubscribes Ordering.OrderPlacedan IHandleOrderPlaced handler interface with the fully-qualified event typeKitchen, Delivery, Payment

A context may even subscribe to two same-named events from different publishers (e.g. both Sales.Shipped and Returns.Shipped). The bare IHandleShipped seam would collide, so each colliding handler is qualified by its publisherIHandleSalesShipped and IHandleReturnsShipped, each typed on its own publisher’s event. The common single-publisher case keeps the bare IHandle<Event> name unchanged. The same publisher-qualification applies in the TypeScript and PHP emitters; Python emits no per-subscription handler seam, so its same-named events simply live in their distinct context packages.

See model versioning and the evolving a model tutorial.

Construct.koi syntax (short)EmitsDemo location
Context versioncontext Catalog version 2 { … }metadata only (glossary heading + @since ceiling check); byte-identical C#Menu version 2
@since(n)@since(2) barcode: String?no C#; surfaces in the glossary as since v2; warns (KOI1501) if above the context versionPizza.kcal @since(2)
@deprecated("reason")@deprecated("use amount") legacyAmount: Decimal[Obsolete("reason")] on the property/class + using System;deprecation markers
Backward-compat checkkoine check v2 --baseline v1compares published surfaces; exits non-zero on breaking changesexamples/versioning/

Not language constructs, but the commands and editor support that make .koi pleasant to write. See the CLI reference and editor tooling.

ToolInvocationWhat it doesReference
Formatterkoine fmt <path> [--check]Canonically, idempotently reformats .koi in place; --check verifies without writing (CI gate)CLI
Project scaffoldkoine init [dir] [--force]Writes a buildable starter domain.koi, koine.config, and README.md; --force overwritesCLI
Watch modekoine watch <path> [--out <dir>]Re-emits (or re-validates) on every .koi change with debounced fast feedbackCLI
Language serverkoine lspLSP over stdio: live diagnostics, hover, completion, and cross-file go-to-definitionEditor tooling
TextMate grammar(editor extension)Syntax highlighting for .koi in VS Code and RiderEditor tooling