Two services, their application and the program that serves them

The complete source of structure_services.ek9, compiled by the EK9 compiler. Shown on structure.html.

#!ek9
defines module site.structure.services

  defines component

    BusinessProcess as abstract
      process() as abstract
        -> detail as String
        <- rtn as String?

    StandardBusinessProcess extends BusinessProcess
      override process()
        -> detail as String
        <- rtn as String: `{"processed": "${detail}"}`

  defines function

    //Every operation below answers through this one response, to keep the example short.
    jsonResponse() as pure
      -> json as String
      <- response as HTTPResponse: (json) with trait of HTTPResponse
        override content()
          <- rtn as Bytes: Bytes().withUtf8(json)
        override status() as pure
          <- rtn as Integer: 200
        override contentType() as pure
          <- rtn as String: "application/json"
        override cacheControl() as pure
          <- rtn as String: "no-cache"
        override contentLanguage() as pure
          <- rtn as String: "en"
        default operator ?

  defines service

    Addresses for :/addresses open

      //A GET (the default verb for a named operation)
      byId() for :/{address-id}
        ->
          addressId as String :=: PATH "address-id" //required because different name
          acceptLanguage as String :=: HEADER "Accept-Language"
        <-
          response as HTTPResponse: jsonResponse(`{"id": "${addressId}", "language": "${acceptLanguage}"}`)

      listByPostCode() for :/{postCode}/
        ->
          postCode as String //Now assume PATH PARAM because there is a param
          acceptLanguage as String :=: HEADER "Accept-Language"
        <-
          response as HTTPResponse: jsonResponse(`{"postCode": "${postCode}", "language": "${acceptLanguage}"}`)

      list() for :/
        ->
          postCode as String :=: QUERY "postCode"
          acceptLanguage as String :=: HEADER "Accept-Language"
        <-
          response as HTTPResponse: jsonResponse(`{"query": "${postCode}", "language": "${acceptLanguage}"}`)

      //A POST adds to the collection
      operator += for :/
        -> request as HTTPRequest :=: REQUEST
        <-
          response as HTTPResponse: jsonResponse(request.content().toText("utf-8"))

      //A DELETE removes from it
      operator -= for :/{addressId}
        -> addressId as String
        <-
          response as HTTPResponse: jsonResponse(`{"deleted": "${addressId}"}`)

      //A PUT replaces a resource
      operator :^: for :/{addressId}
        ->
          addressId as String //assume PATH
          content as String :=: CONTENT
        <-
          response as HTTPResponse: jsonResponse(`{"replaced": "${addressId}", "with": ${content}}`)

      //merge is a PATCH
      operator :~: for :/{addressId}
        ->
          addressId as String //assume PATH
          content as String :=: CONTENT
        <-
          response as HTTPResponse: jsonResponse(`{"merged": "${addressId}", "with": ${content}}`)

    //Here is customer service (only partial)
    //But shows verbs 'GET', 'PUT' etc named explicitly
    Customers for :/customers open

      //Just get by customer identifier
      //i.e http(s):somehostorother.com/customers/CUST-ABCD
      byId() as GET for :/{customerId}
        ->
          customerId as String :=: PATH
          acceptLanguage as String :=: HEADER "Accept-Language"
        <-
          response as HTTPResponse: jsonResponse(`{"customer": "${customerId}", "language": "${acceptLanguage}"}`)

      replace() as PUT for :/{customerId}
        ->
          customerId as String :=: PATH
          content as String :=: CONTENT
        <-
          response as HTTPResponse: (customerId, content) with trait of HTTPResponse
            businessProcess as BusinessProcess! // this gets injected as a component
            //Now use the business process to build the response.
            override content()
              <- rtn as Bytes: Bytes().withUtf8(businessProcess.process(`${customerId} ${content}`))
            override status() as pure
              <- rtn as Integer: 200
            override contentType() as pure
              <- rtn as String: "application/json"
            override cacheControl() as pure
              <- rtn as String: "no-store"
            override contentLanguage() as pure
              <- rtn as String: "en"
            default operator ?

  //Application that exposes the web services to be made available.
  defines application

    CustomerAccess
      //Register the business component
      register StandardBusinessProcess() as BusinessProcess

      //Now register the services to be made available.
      register Customers()
      register Addresses()

  //A program that starts up the REST server with the configuration above
  defines program

    RestWebApp() with application of CustomerAccess
      stdout <- Stdout()
      stdout.println("Now starting REST Services")

      //Serve on socket port 9092, until the server is stopped.
      try
        -> server <- HTTP(NetworkProperties(9092))
        server.serve()

Download structure_services.ek9