A CQRS library to support separation of commands and queries on the type level.
elm install jamesrweb/elm-cqrs
You interact with this library via the Cqrs.Command
or Cqrs.Query
module.
It is important that your API responds in the following pseudocode format for both commands and queries:
type alias ApiSuccess a =
{ data : a
}
type alias ApiError e =
{ error : e
}
- If an
error
is present, the failure variant forCqrs.Command.Response
orCqrs.Query.Response
will be decoded. - If the
data
key is present, the success variant for the givenCqrs.Query.Response
will be decoded. - If the
data
key is present in aCqrs.Command.Response
, it will be discarded since commands, by definition, do not return data.
Please see the tests for more examples of how the decoders handle the different cases.
A command is an instruction which returns no value in response. It is simply successful or not.
To send a command, you can do the following:
import Cqrs.Command as Command exposing (CommandResponse)
import User exposing (User) -- An example module
import Api.Error as Error exposing (Error) -- An example module
type alias UserCommandResponse =
CommandResponse Error ()
type Msg =
UserCommandExecuted UserCommandResponse
addUserCommand : User -> Cmd Msg
addUserCommand user =
let
body : Json.Encode.Value
body =
User.encode user
in
Command.request "/api/v1/user" Error.Http Error.Unknown body Error.decoder UserCommandExecuted
addUserCommandTask : User -> Task () UserCommandResponse
addUserCommandTask user =
let
body : Json.Encode.Value
body =
User.encode user
in
Command.requestTask "/api/v1/user" Error.Http Error.Unknown body Error.decoder
A query is a request to look up a value or series of values in response. It will either return the desired values, or an appropriate error.
To execute a query, you can do the following:
import Cqrs.Query as Query exposing (QueryResponse)
import UUID exposing (UUID) -- elm install TSFoster/elm-uuid (for example)
import Models.User exposing (User) -- An example module
import Api.Error as Error exposing (Error) -- An example module
type alias UserQueryResponse =
QueryResponse Error User
type Msg =
UserQueryExecuted UserQueryResponse
findUserQuery : Uuid -> Cmd Msg
findUserQuery id =
let
path : String
path = "/api/v1/user/" ++ Uuid.toString id
in
Query.request path Error.Http Error.Unknown UserQueryExecuted User.decoder Error.decoder
findUserQueryTask : Uuid -> Task () UserQueryResponse
findUserQueryTask id =
let
path : String
path = "/api/v1/user/" ++ Uuid.toString id
in
Query.requestTask path Error.Http Error.Unknown User.decoder Error.decoder
In both cases, Cqrs.Request.command
and Cqrs.Request.query
, you can pass an optional http error handler in case you need custom errors or logging to be done, for example.
To create a custom http error handler, you need to implement a function which takes an error of type e
and returns a mapped value of type f
which is probably some domain value but could be anything, for example being given an Http.Error
and returns an ApiError
domain error. For example:
module Api.Error exposing (Error, errorMapper, defaultError)
import Http -- elm install elm/http
type Error =
Unknown
| Http Http.Error
httpErrorMapper : Http.Error -> Error
httpErrorMapper error =
Http error
defaultError : Error
defaultError =
Unknown
And then to use it in your requests, you pass it in the second parameter to either Cqrs.Request.command
or Cqrs.Request.query
, like so:
import Cqrs.Query as Query
import Cqrs.Command as Command
import Api.Error exposing (httpErrorMapper, defaultError)
Query.request url httpErrorMapper defaultError ...
Command.request url httpErrorMapper defaultError ...