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.

The EK9 IDE as the lesson opens

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 EK9 IDE once this is done: 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.

The EK9 IDE once this is done: 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