Service Routes and DI Wiring
A small shop: catalogue.ek9 holds two web services - ItemService, behind a security gate called AccountGate, and the open HealthService - and Catalogue, a class that needs a Repository and a Notifier injected; components.ek9 holds those and their implementations; app.ek9 registers everything in the ShopApp application. These views read the compiled program, so nothing is run and no port is opened. Read the files, then start when you are ready. To play it in the IDE: Help > Lessons > Understanding a codebase…, or ek9 -ide -lesson services-and-di.
Start from
The code the lesson starts from: copy it into a file of your own and follow the steps.
app.ek9
#!ek9 defines module lesson.shop.app references lesson.shop.catalogue::HealthService lesson.shop.catalogue::ItemService lesson.shop.components::ConsoleNotifier lesson.shop.components::MemoryRepository lesson.shop.components::Notifier lesson.shop.components::Repository defines application ShopApp register MemoryRepository() as Repository register ConsoleNotifier() as Notifier register ItemService() register HealthService() defines program ServeShop() with application of ShopApp props <- NetworkProperties(port: 8080, localOnly: true) HTTP(props).serve() //EOF
catalogue.ek9
#!ek9 defines module lesson.shop.catalogue references lesson.shop.components::Notifier lesson.shop.components::Repository defines constant okStatus <- 200 jsonType <- "application/json" plainType <- "text/plain" noCache <- "no-cache" langEn <- "en" defines class Catalogue repo as Repository! notifier as Notifier! howMany() <- rtn as Integer: repo.count() lookup() -> name as String <- rtn as String: name notifier.notify(`Looked up ${name}`) default operator ? defines function AccountGate is SecurityGate -> context as HTTPContext <- rtn as HTTPContext: context defines service ItemService for :/items constrain by AccountGate howMany() as GET for :/ <- response as HTTPResponse? count <- Catalogue().howMany() response: (count) with trait HTTPResponse override content() <- rtn as Bytes: Bytes().withUtf8(`{"count": ${count}}`) override status() as pure <- rtn as Integer: okStatus override contentType() as pure <- rtn as String: jsonType override cacheControl() as pure <- rtn as String: noCache override contentLanguage() as pure <- rtn as String: langEn default operator ? byName() as GET for :/{name} -> name as String <- response as HTTPResponse? wanted <- Catalogue().lookup(name) response: (wanted) with trait HTTPResponse override content() <- rtn as Bytes: Bytes().withUtf8(`{"name": "${wanted}"}`) override status() as pure <- rtn as Integer: okStatus override contentType() as pure <- rtn as String: jsonType override cacheControl() as pure <- rtn as String: noCache override contentLanguage() as pure <- rtn as String: langEn default operator ? HealthService for :/health open ping() as GET for :/ <- response as HTTPResponse: () with trait HTTPResponse override content() <- rtn as Bytes: Bytes().withUtf8("ok") override status() as pure <- rtn as Integer: okStatus override contentType() as pure <- rtn as String: plainType override cacheControl() as pure <- rtn as String: noCache override contentLanguage() as pure <- rtn as String: langEn default operator ? //EOF
components.ek9
#!ek9 defines module lesson.shop.components defines component Repository as abstract count() as abstract <- rtn as Integer? default operator ? MemoryRepository extends Repository override count() <- rtn as Integer: 2 default operator ? Notifier as abstract notify() as abstract -> message as String default operator ? ConsoleNotifier extends Notifier override notify() -> message as String stdout <- Stdout() stdout.println(message) default operator ? //EOF
Step 1: Service Routes
Choose Analyse > Service Routes.
The summary line counts the surface: 3 routes, 2 guarded and 1 unguarded, and 1 unsanitised parameter - red, because an unguarded route and an unsanitised input are the two things a security review looks for. Each service is a box; each route a card with its verb, its full path (a {name} segment is picked out), the method that handles it and its gate. HealthService's GET /health shows unguarded - it is declared open, so that is a decision you can now see rather than an oversight to hunt for. ItemService's two routes show AccountGate, and /items/{name} also lists its name:PATH parameter. Click a method, a gate or a parameter to jump to its source; the Search, Verb and Service controls filter the cards.
Step 2: DI Wiring
Choose Analyse > DI Wiring.
ShopApp is the application, with a HEALTHY border: everything it needs is provided. Its registrations come first - MemoryRepository registered as Repository and ConsoleNotifier as Notifier, and the two services - then the injection sites in the program: Catalogue.repo needs a Repository and Catalogue.notifier a Notifier, each ticked because a registration supplies it. A site nothing supplies would be drawn red instead. Click any row to open its source.
The result
That is a web service and its wiring, read without running either: Service Routes listed every route the compiler found - with its security gate, or the red unguarded flag - and DI Wiring showed what ShopApp registers and whether every injection site has something to receive. Nothing was served - ServeShop, the program that would open a port, was never run. AI assistants get the same data from ek9_query_service_routes and ek9_query_dependency_injection.
Where this lesson comes from
- Answers -
ek9 -qand the IDE's Answers tab: 1292, 203. - For AI assistants - the MCP tool
ek9_query_service_routes,ek9_query_dependency_injection.
In the EK9 reference: Dependency Injection in EK9, Design note: from components to microservices, Web Services in EK9


