Mu-Haskell 0.4: OpenAPI and Instrumentation

Mu-Haskell 0.4: OpenAPI and Instrumentation

There has been a lot happening during the past six months, not only around the world, but also in Mu-Haskell. In fact, we are happy to announce the 0.4 release of the library! Apart from many bug fixes, we have worked on instrumentation (metrics and distributed tracing), and we received a wonderful contribution by andremarianiello that led us to support OpenAPI / Swagger / REST services in Mu.

OpenAPI / REST

Known by many names – RESTful services, OpenAPI, Swagger – the idea is always the same: use the HTTP infrastructure for RPC calls. In the same way that your browser requests a website, you can request data or action from a web server. JSON is commonly use as serialization format for the data being transmitted.

Haskell already has a very good library for developing REST web services, named Servant. This library follows a similar design to Mu’s in that the API for the web service is defined in the type level. The following code defines a route /greet whose parameters are given as JSON in the request body, and which can be accessed using the POST verb.

type ServiceAPI = "greet" :> ReqBody '[JSON] Name :> POST '[JSON] String

With this new release, you can automatically transform Mu type level service definitions into Servant type level APIs. In addition, if you have a Mu server, it can be transformed into a Servant one, which, in practice, means exposing it through a RESTful interface.

From the point of view of Servant, a Mu server is missing an important piece of information: the route and verb in which each method should be available. The code contributed by andremarianiello gives programmers a way to link this information by using mu-schema annotation support.

type instance AnnotatedPackage ServantRoute QuickstartService
  = '[ 'AnnService "Greeter" ('ServantTopLevelRoute '["greet"])
     , 'AnnMethod "Greeter" "SayHello"
                   ('ServantRoute '["say", "hello"] 'POST 200),
     ]

The previous block of code instructs Servant where to expose the whole service, and then the route to each single method. As a result, SayHello is available by directing a POST request to /greet/say/hello. Once this code is in place, you just need to call servantServerHandlers to turn a Mu server into a Servant one.

The conversion to Servant works not only for simple unary RPC calls, but also for more complex streaming methods. Check the documentation for more information.

Swagger UI

One of the niceties of exposing an OpenAPI / Swagger definition for your service is that many tools can automatically derive clients, like Bow OpenAPI or OpenAPI Generator. It can also present a very nice documentation interface by means of Swagger UI. The Servant community has, in fact, created a package for this purpose.

By adding just one more line of type level code, and one more line of term level code, your RESTful web service will shine with colors!

  type instance AnnotatedPackage ServantRoute QuickstartService
+   = '[ 'AnnPackage ('ServantAdditional (SwaggerSchemaUI "swagger-ui" "swagger.json"))
       , {- rest of annotations -} ]

  server = servantServerHandlersExtra
+            (swaggerSchemaUIServer (swagger svc))
             toHandler svc

Instrumentation

Knowledge and data are key to making good decisions in relation to your services. In the 0.4 release of Mu-Haskell, we have worked on ways for you to recollect this data. In particular, we integrate with the well-known Prometheus for metrics, and Zipkin for distributed tracing.

This was not a scenario we had considered in our initial design, which has led to some restructuring of the code, which, in some cases, may require some manual intervention during the upgrade from 0.3 to 0.4. But now we have the notion of server wrapper, which is a way to extend a Mu server with additional functionality.

The following code block instruments server using both libraries. In both cases, the steps to follow are quite similar: perform some (impure) initialization, put together the final server by wrapping the original one with each additional instrumentation source, and finally run it.

main = do
  -- Initialize Prometheus and Zipkin
  met <- initPrometheus "health"
  zpk <- newZipkin defaultZipkinSettings
           { settingsPublishPeriod = Just 1
           , settingsEndpoint = Just $ Endpoint (Just "me") Nothing Nothing Nothing }
  -- Put together the server
  let s = zipkin (MuTracing alwaysSampled "health-check")
            $ prometheus met $ server
  -- And run it!
  run 50051 (prometheusWai ["metrics"]
               (gRpcAppTrans msgProtoBuf (runZipkin zpk) s))

Unfortunately, we do not have a design that makes the last two lines easier to write yet. In particular, we have to remember that Prometheus requires a new route to be exposed, whereas Zipkin operates as a monad transformer, hence the need for gRpcAppTrans.

Conclusion

With the release of Mu-Haskell 0.4, we now have support for all the major communication protocols: gRPC, GraphQL, and now REST / OpenAPI! In fact, you can expose a single service in the three protocols concurrently. Check the examples/health-check folder in the repository for an example.

We also provide support for logging, metrics, and distributed tracing; key components for a healthy service infrastructure. We’d love to hear any further suggestions about other technologies in this realm that we could connect to.

The active development of Mu-Haskell is proudly sponsored by 47 Degrees, a Functional Programming consultancy with a focus on the Scala, Kotlin, Swift, and Haskell programming languages.

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.