How does it feel to write web backend in fp (haskell) for pythonist.
Three kind of errors I spend too much of time to resolve (but it was simple).
One day you decided that it's time to start write on Haskell. After learning all the basics, you ready to create “my_first_haskell_backend_app”. There are problems you probably meet with (but after this article don't have to).
Parameterized App monad
When you work on at least some complex application, it’s most convenient to use parameterized app monad. Firstly, what the approaches does app monad give? It describes main abilities of your applications, like logging and access to config with reader monad (using monad stack and type classes).
Secondly, why should app monad be parametrized in functions? It not only allows you to see the rules your monad follows, but to have highly reusable function: you can change the monad to anything, which follows the rules. Like here, so we know, that we can use logger and there is an IO actions.
type GetMap m = (Logger m, MonadIO m) => [ProductId] -> m (Either ErrDAO ProductPricesMap)
So, what the problem did I meet with?
There are two functions. First is for working with queue and write to db (_newOrderProcessor), second is for starting that function in separated thread (eventPipeProcessorRunner).
What was the first variant:
eventPipeProcessorRunner :: MonadIO m => EventPipes -> EventPipeProcessor m -> m ()
eventPipeProcessorRunner eventPipes processor = do
void $ forkIO $ forever $ _newOrderProcessor processor (_newOrdersPipe eventPipes)
And what error did it end with:
Couldn’t match type ‘m’ with ‘IO’ ‘m’ is a rigid type variable Expected type: m () Actual type: IO ()
All functions including a domain logic are wrote with parameterized app monad and to use them in main, you have to transform your parameterized monad m to IO, unwrap a monad stack.
In my example there’ll be runner to resolve this moment. And the next step is to change returning m to IO. Of course, when you create another thread, it returns exactly IO.
At the end we have this, compiled and working. Now It is impossible to imagine, that it took several weeks to understand!
(don’t look at (MonadUnliftIO m, m ~ IO) types, it was some intermediate attempt)
Swagger
If you want to add Swagger to your API, you should transform you API type to Swagger Handler with toSwagger function.
todoSwagger :: Swagger
todoSwagger = toSwagger appAPI
& info.title .~ "Registry API"
& info.version .~ "1.0"
& info.description ?~ "This is an API that tests swagger integration"
& info.license ?~ ("MIT" & url ?~ URL "http://mit.com")
But after all things done I have such a message:
No instance for (ToSchema Swagger) arising from a use of ‘toSwagger’
Looks like I missed some derrivings from tutorial (really not), ok, lets do them.
instance ToSchema Swagger where
declareNamedSchema _ = pure $ NamedSchema (Just "Swagger") mempty
It works for the simplest example, but if we want to have swagger with UI, problems don’t end and derrivings don’t work.
No instance for (ToSchema (Servant.Swagger.UI.Core.SwaggerUiHtml “swagger-ui” (“swagger.json” :> Get ‘[JSON] Value))) arising from a use of ‘toSwagger’
After trying to install different versions of this library, I gave up, rised a ticket on github and went to sleep. Next day I realized, that passed not only my API, but the swagger’s too.
type AppAPI =
SwaggerSchemaUI "swagger-ui" "swagger.json" --separate this part
:<|> ... --Rest of the API
Postgresql interaction (hasql)
This library was mentioned in Bragilevsky’s book as one of the fastest, usable and with width ecosystem, but not so simple as postgresql-simple and not so complicated as ORM-like stuff (Opaleye, Selda). Another idea I was interested in is explicitly transaction which is represented here. It seems to be a good start point! And nevertheless, there is something to be complain with.
We can follow this tutorial. It describes the main ideas and abstraction levels. nikita-volkov/hasql-tutorial1: Tutorial on organisation of Hasql code in a Postgres-integration layer (github.com)
But it rather old, the newest version of hasql-transaction has another API. Ok, we can dive into tests and find example here. hasql-transaction/conflicts-test/Main.hs at master · nikita-volkov/hasql-transaction (github.com)
At this point we have no problem to write statements and wrap them into transaction. But still every example uses simple types for decoding and encoding. What if we want to use our data types?
from simple
Statement (Int64, Int64) Int64
to complex
Statement (D.OrderId, IN.NewOrderItemDTO) D.OrderItem
And here a “long story short” starts, consist of ChatGPT, Google, issue’s tab under repositories mess. In the end I came from this:
encoder =
(fst >$< Encoders.param (Encoders.nonNullable Encoders.int8))
<> (snd >$< Encoders.param (Encoders.nonNullable Encoders.int8))
to this:
encoder =
contramap ((\(D.OrderId a) -> fromIntegral a) . fst)(ENC.param (ENC.nonNullable ENC.int8))
<> contramap ((\(D.ProductId a) -> fromIntegral a) . IN.nOrItProductId . snd)(ENC.param (ENC.nonNullable ENC.int8))
where part with lambda was founded in mapping custom type example in hasql-th’s README.
And from this:
decoder = Decoders.column (Decoders.nonNullable Decoders.int8)
to this:
decoder = DEC.column (DEC.nonNullable (fmap (D.OrderId . fromIntegral) DEC.int8))
where the part with fmap was founded in an article with self-described name “Haskell for madmen: Connecting to a database”.
Conclusions
Nothing can explain thoughts of developer than developer itself:
I didn’t invest into making examples, because I thought the types in the library kinda made it obvious how to use it. However I do believe that it’s always better to have examples than not. I will happily accept a PR with such an example.
And I tend to agree with him. There are not so many tutorials which hold the hand through all the steps but rather force you to go deeper in understanding of tools and language’s features you use. And it makes a head to boil up, as well as feels you like Indiana Jones overcoming challenges on the way to grail: understanding of Haskell philosophy.
If you interested in full project, this is the link to github: [tempuku/registry-haskell (github.com)](https://github.com/tempuku/registry-haskell). I still try to make it better, so it’s opened for your advice.