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: Haskell’s lazy evaluation and runtime system can sometimes make it challenging to profile and optimize performance issues. Identifying and resolving bottlenecks may require a deeper understanding of Haskell internals.

  • Limited Tooling: While Haskell has a range of development tools, even some specialized in web development (like Spock or Scotty), the number of options available for developers is not that significant. Especially when compared to other ecosystems, such as JavaScript’s, where the situation is the opposite, having multiple options for every single library/framework available.

  • 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 for microservices with low-latency requirements. While Haskell is suitable for many applications, some specific use cases may require careful consideration.

  • Limited Corporate Backing: Haskell doesn’t have the same level of corporate backing as some other languages, which can impact the availability of resources, support, and enterprise adoption.

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 DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

module Models where

import Data.Text
import GHC.Generics
import Data.Aeson (ToJSON (..), object, FromJSON (..), (.=), (.:), Value(..), encode)

-- Define a Product data type
data Product = Product
  { productId :: Int
  , productName :: Text
  , productDescription :: Text
  , productPrice :: Double
  } deriving (Show)

instance ToJSON Product where
  toJSON product = object
    [ "id" .= productId product
    , "name" .= productName product
    , "description" .= productDescription product
    , "price" .= productPrice product
    ]

instance FromJSON Product where
  parseJSON (Object v) = Product
    <$> v .: "id"
    <*> v .: "name"
    <*> v .: "description"
    <*> v .: "price"
  parseJSON _ = fail "Invalid Product JSON"

				
			

Notice we’re also defining the “ToJSON” and “FromJSON” methods, to make sure we properly parse the data back and forth.

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 #-}

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 id products = find (\product -> productId product == id) 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.

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