Server implementation
==============
An :wahackage:`ApiContract ` is just a schematic representation of your API service. We still need to implement our handlers that actually does the work. You would have already read about this in the :doc:`start` section.
Implementation of a contract consists of
* Writing a :wahackage:`WebApiServer ` instance.
* Writing :wahackage:`ApiHandler ` instances for all your end-points.
Writing WebApiServer instance
-------------------------------------
The :code:`WebApiServer` typeclass has
- Two associated types
- **HandlerM** - It is the type of monad in which our handler should run (defaults to :code:`IO`).
This monad should implement :code:`MonadCatch` and :code:`MonadIO` classes.
- **ApiInterface** - :code:`ApiInterface` links the implementation with the contract. This lets us have
multiple implementations for the same contract
- One method
- **toIO** - It is a method which is used to convert our handler monad's action to :code:`IO`.
(defaults to :code:`id`)
Let's define a type for our implementation and write a :code:`WebApiServer` instance for the same.
::
data MyApiServiceImpl = MyApiServiceImpl
instance WebApiServer MyApiServiceImpl where
type HandlerM MyApiServiceImpl = IO
type ApiInterface MyApiServiceImpl = MyApiService
toIO _ = id
.. note:: You can skip writing :code:`HandlerM`'s and :code:`toIO`'s definitions if
you want your :code:`HandlerM` to be :code:`IO`.
Writing instances for your handlers
------------------------------------
Now we can write handler for our :code:`User` route as ::
instance ApiHandler MyApiServiceImpl POST User where
handler _ req = do
let _userInfo = formParam req
respond (UserToken "Foo" "Bar")
:code:`handler` returns a :code:`Response`. Here we used :code:`respond` to
build a :code:`Success` :wahackage:`Response`.
You can use its counter-part :code:`raise` as discussed in :doc:`error-handling`
to send :code:`Failure` :wahackage:`Response`
Doing more with your handler monad
----------------------------------
Though the above implementation can get you started, it falls short for many
practical scenarios. We'll discuss some of them in the following sections.
Adding a config Reader
~~~~~~~~~~~~~~~~~~~~~~
Most of the times our app would need some kind of initial setting which could
come from a config file or some environment variables. To accomodate for that, we
can change :code:`MyApiServiceImpl` to ::
data AppSettings = AppSettings
data MyApiServiceImpl = MyApiServiceImpl AppSettings
Just adding :code:`AppSettings` to our :code:`MyApiServiceImpl` is useless unless our
monad gives a way to access those settings. So we need a monad in which we can
read these settings, anytime we require. A :code:`ReaderT` transformer would fit
perfectly for this scenario.
For those who are not familiar with :code:`Reader` monad, it is a monad
which gives you read only access to some data(say, settings) throughout a computation.
You can access that data in your monad using :code:`ask`. :code:`ReaderT` is a
monad transformer which adds capabilities of :code:`Reader` monad on top of
another monad. In our case, we'll add reading capabilities to :code:`IO`. So the
monad for our handler would look something like ::
newtype MyApiMonad a = MyApiMonad (ReaderT AppSettings IO a)
deriving (Monad, MonadIO, MonadCatch)
Note: :code:`HandlerM` is required to have :code:`MonadIO` and :code:`MonadCatch`
instances. Thats why you see them in the :code:`deriving` clause.
There is still one more piece left, before we can use this. We need to define
:code:`toIO` function to convert :code:`MyApiMonad`'s actions to :code:`IO`.
We can use `runReaderT `_ to pass the initial :code:`Reader`'s environment settings
and execute the computation in the underlying monad(IO in this case). ::
toIO (MyApiServiceImpl settings) (MyApiMonad r) = runReaderT r settings
So the :code:`WebApiServer` instance for our modified :code:`MyApiServiceImpl`
would look like: ::
instance WebApiServer MyApiServiceImpl where
type HandlerM MyApiServiceImpl = MyApiMonad
type ApiInterface MyApiServiceImpl = MyAppService
toIO (MyApiServiceImpl settings) (MyApiMonad r) = runReaderT r settings
A sample :code:`ApiHandler` for this would be something like: ::
instance ApiHandler MyApiServiceImpl POST User where
handler _ req = do
settings <- ask
-- do something with settings
respond (UserToken "Foo" "Bar")
.. _implementation:
Adding a logger
~~~~~~~~~~~~~~~
Adding a logging system to our implementation is similar to adding a :code:`Reader`.
We use :code:`LoggingT` transformer to achieve that. ::
newtype MyApiMonad a = MyApiMonad (LoggingT (ReaderT AppSettings IO) a)
deriving (Monad, MonadIO, MonadCatch, MonadLogger)
instance WebApiServer MyApiServiceImpl where
type HandlerM MyApiServiceImpl = MyApiMonad
type ApiInterface MyApiServiceImpl = MyAppService
toIO (MyApiServiceImpl settings) (MyApiMonad r) = runReaderT (runStdoutLoggingT r) settings