What are you looking for?

How to Write Microservices in Haskell

Explore microservices in other languages

Writing Microservices in Haskell

Microservices have become the go-to architectural approach for building scalable and flexible applications. 

But here’s the twist: What if you could embark on this microservices journey with a touch of elegance and precision?

Welcome to the world of Haskell, where functional programming meets the microservices paradigm. If you’re curious, excited, or just plain intrigued about writing microservices in Haskell, you’ve come to the right place. 

In this article, we’ll explore how you can use this powerful, statically typed language to craft your microservices.

So, grab your mandatory cup of coffee, and let’s dive head-first into the world of Haskell microservices. 

Our promise is that you’ll leave with a fresh perspective on how to create robust, maintainable, and efficient microservices that’ll have you picking Haskell from now on!

Decoding Microservices: Best Practices Handbook for Developers

Learn how to overcome microservices challenges by following some best practices

Pros and cons of building microservices with Haskell

While there is no single language that is perfect for  programming language  Haskell is definitely not one of the common choices. Historically, it’s been used as a teaching tool in universities to teach students about functional programming.

However, as you’re about to find out, Haskell is quite more powerful than that.

Here are some of the benefits of choosing this language as your weapon of choice for your next microservice:

  • Strong Static Typing: Haskell is known for its strong static type system, which helps catch errors at compile-time, reducing runtime bugs and making your microservices more reliable. Granted, strong typing is not a mandatory feature in any language, and you can build complex systems without it, but it does provide an extra set of eyes to help you avoid minor bugs.
  • Functional Programming: Haskell is a pure functional programming language, which encourages writing pure, side-effect-free functions. This can lead to cleaner and more maintainable code in microservices.
  • Concurrency and Parallelism: Haskell has built-in support for concurrent and parallel programming. This is crucial for microservices when you need to handle multiple requests simultaneously, making efficient use of system resources.
  • Immutability: Haskell promotes immutability, which can make it easier to reason about the state of your microservices. It also helps prevent common concurrency-related bugs.
  • High Performance: Haskell’s runtime system is highly optimized, which can lead to competitive performance. Microservices built in Haskell can be both efficient and scalable.
  • Quick Development Cycle: Haskell’s type inference and expressive syntax can speed up development by reducing the need for writing extensive boilerplate code. This is especially useful when you’re rapidly developing and deploying microservices.
  • Rich Tooling: Haskell has a range of development tools, such as the Glasgow Haskell Compiler (GHC), that helps with profiling, debugging, and optimization.
As already mentioned, there is no “perfect” solution, and sadly, there are probably more “cons” than “pros” when it comes to building microservices in this particular language. Let’s analyze some of the most common problems around using Haskell for this task:
  • Learning Curve: Haskell’s unique functional programming paradigm and strong type system can be challenging for developers who are not familiar with the language. It may take some time to learn and become proficient in Haskell, thus significantly increasing development times if this is your (or your team’s) first Haskell project.
  • Limited Pool of Developers: The Haskell developer community is smaller compared to mainstream languages like Java, Python, or JavaScript. Finding experienced Haskell developers can be more challenging, and team training may be necessary.
  • Performance Profiling Complexity: While profiling can be a complex task in any language, Haskell’s lazy evaluation and runtime system don’t really help the language stand out in this regard. Identifying and resolving bottlenecks will most likely require a deeper understanding of Haskell internals (as is the case with many other languages). Luckily, there are solutions like ThreadScope or the Criterion micro-benchmarking tool that try to soften the experience.
  • Limited Tooling: The Haskell ecosystem is smaller compared to those of Java or JavaScript and certain areas (such as standalone GUI applications, mobile app development, and systems/embedded programming) have limited tooling options. However, Haskell is fantastic in server-side web programming and service backends. Frameworks like Spock and Scotty are supported by high-performance HTTP servers such as WARP, which outperforms some of the best servers in the field, like NGINX.

    Additionally, the Haskell ecosystem offers options for various backend use cases, including REST and Web APIs, database access, event processing, streaming and queuing systems, data codecs, cryptography, blockchain, and bindings for machine learning libraries like scikit-learn, PyTorch, and TensorFlow. Overall, the central Hackage Repository hosts approximately 24,000 packages across over 900 categories. While that number may not be as extensive as in larger ecosystems, the robust support for backend development makes Haskell a choice for service-oriented applications.

  • Community and Documentation: While the Haskell community is passionate and helpful, the resources and documentation for microservices development in Haskell may not be as abundant as for more mainstream languages.
  • Garbage Collection: Haskell’s garbage collection strategy may not always be ideal, although, since GHC’s version 8.10.1, there have been considerable improvements, making Haskell’s garbage collector perform quite well in near-real-time production environments. While Haskell is suitable for many applications, it’s important to be aware of the limitations and remember that some specific use cases may require careful consideration.
  • Limited Corporate Backing: While some might see this as a good thing, giving Haskell’s community complete freedom in deciding how to move the language forward (following Haskell’s ironic motto of “avoid success at all costs”), it’s also a double-edged sword, considering how corporate backing also acts as a type of social proof for software developers, ensuring that the language has strong support when required. That said, Haskell is not without some corporate backing, with figures like Github, Meta, and even Google in different capacities.

As you can appreciate, the main issues with using Haskell as the main programming language for microservices, aren’t completely technical. Instead, they come from the fact that Haskell doesn’t share the popularity of other “mainstream” languages, and because of that, it has less community support, less tooling, and simply less information generated around the use case.

Will that dictate your decision? That is completely up to you. As you’re about to see, building a microservice using Haskell is completely possible and requires relatively less code than using other alternatives.

Building a microservice in Haskell

In this hands-on example, we’ll create a simplified product catalog microservice using Haskell and the Scotty web development framework. This microservice will expose RESTful endpoints for managing product information.

Step 1: Project setup

Assuming you’ve already installed Haskell and the Haskell Toolchain on your system, the next thing to do is to create your project’s template and add Spock as a dependency into it.

First, create the project with the following line:

stack new microservice-example simple

Then once inside the folder, edit the microservice-example.cabal file to add several dependencies inside the build-depends section.

It should look like this:

    
      build-depends:       base >= 4.7 && < 5,
                       scotty,
                       text,
                       aeson,
                       http-types

    
   

Now, you’re ready to build the project to double-check everything is properly configured.

Use the following line and make sure the result is successful (it might take a while to finish the first time around):

stack build

If there are no errors, you should be in good shape to continue.

Step 2: Define the data model

We’re going to create a very simple Product class in Haskell and we’re going to save it into a separate file called “Models.hs”:

    
     {-# LANGUAGE DeriveAnyClass        #-}
{-# LANGUAGE DeriveGeneric         #-}
{-# LANGUAGE DuplicateRecordFields #-}

module Models (Product (..), User (..)) where

import           Data.Aeson
import           Data.Text
import           GHC.Generics

-- Define a Product data type
data Product = Product
  { id          :: Int,
    name        :: Text,
    description :: Text,
    price       :: Double
  }
  deriving (Show, Generic, ToJSON, FromJSON)

-- Define a User data type, which uses same attribute names as Product
-- This is made possible by the DuplicateRecordFields extension
data User = User
  { id          :: Int,
    name        :: Text,
    description :: Text
  }
  deriving (Show, Generic, ToJSON, FromJSON)


    
   

Notice that you don’t need to manually write implementations for “ToJSON” and “FromJSON” methods. This way Haskell is perfectly capable of providing those methods on its own.

Step 3: Implement RESTful endpoints

Our routes definition is going to be part of the Main.hs file. Here’s where we’ll instantiate the Scotty framework, and where we’ll also define the routes and what each one is doing (their logic).

We’ll be implementing the data storage logic here since we’re keeping the example simple and we’re not using any database connection. Instead, we’ll define a mutable IORef, and we’ll store our products there.

    
     {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedRecordDot #-}



import Web.Scotty
import Network.HTTP.Types (status404)

import Control.Monad.IO.Class (liftIO)
import Data.Text.Lazy (Text)
import Data.IORef
import Data.List (find)
import Models

-- Sample product list
initialProducts :: [Product]
initialProducts =
  [ Product 1 "Product 1" "Description 1" 19.99
  , Product 2 "Product 2" "Description 2" 29.99
  ]


main :: IO ()
main =  do
  -- Create a mutable reference to store the products
  productsRef <- newIORef initialProducts
  scotty 3000 $ do
    -- Define a route to list all products
    get "/products" $ do
      products <- liftIO $ readIORef productsRef
      json products

    -- Define a route to get a product by ID
    get "/products/:id" $ do
      productIdParam <- param "id"
      products <- liftIO $ readIORef productsRef
      let product = findProductById productIdParam products
      case product of
        Just p -> json p
        Nothing -> raiseStatus status404 "not found"
    post "/products" $ do
      newProduct <- jsonData :: ActionM Product
      liftIO $ modifyIORef productsRef (\products -> newProduct : products)
      json newProduct

-- Function to find a product by ID
findProductById :: Int -> [Product] -> Maybe Product
findProductById idx products = find (\product -> product.id == idx) products


    
   

As you can appreciate, the structure is fairly straightforward. Scotty allows us to define each route with a basic string, using a function to describe the HTTP verb. In there, we define how we interact with our original IORef (either reading from it or mutating it).

We’re also loading a couple of hard-coded products out of the gate, but that’s just to get things moving faster.
Another important point to notice, is the use of {-# LANGUAGE OverloadedRecordDot #-} at the start of the file, this allows us to use the “id” attribute in the findProductById function without it colliding with Haskell’s Prelude.id function.

That’s it! You’ve just created a basic product catalog microservice in Haskell using the Scotty web framework.

Of course, this is far from production-ready. If you’d like to extend this into something you can deploy and use, you might want to look into adding authentication (probably using JSON Web Tokens), an actual data layer (probably using something like PostgreDB for instance), and maybe even some pagination.

Orchestrating your microservices

Incorporating Camunda into your Haskell microservices ecosystem exemplifies the principles of precision and functional clarity. 

Camunda extends Haskell’s benefits by providing orchestration that ensures workflows remain consistent with your functional paradigm. 

Its execution engine perfectly complements Haskell’s concurrency and performance traits, managing complex tasks, state, and error handling with mathematical precision. 

As Haskell encourages elegant and concise code, Camunda’s BPMN models offer a similar simplicity in process design and automation, fostering a harmonious alignment of business processes with technical implementation.

Curious to see how Camunda can help you with microservice orchestration? Check out this article about the top 6 benefits of setting up an event-driven process orchestration, and then dive right into Camunda with a free account.

Start orchestrating your microservices with Camunda