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 =
{ -- Must be `True` to successfully decode the `data`, when decoding occurs
success : Bool
, data : a
}
type alias ApiError =
{ success : Bool
, error : String
}
- If an
error
is present, the failure variant forCqrs.Command.Response
orCqrs.Query.Response
will be decoded. - If the
data
is present in the response but thesuccess
value isfalse
, the failure variant forCqrs.Command.Response
orCqrs.Query.Response
will be decoded. - If the
data
key is present in a command response, it will be discarded, since commands do not return data. - If the
success
key isfalse
for a command or a query but theerror
is not set, the decoder will fail with anErr
result.
Please see the tests for more examples of how the decoders handle the different cases.
Furthermore, all urls provided to Cqrs.Query.request
and Cqrs.Query.response
must be absolute URLs.
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
import User exposing (User) -- An example module
type Msg =
UserCommandExecuted Command.Response
baseUrl : String
baseUrl =
"https://example.com"
addUserCommand : User -> Cmd Msg
addUserCommand user =
let
url : String
url = baseUrl ++ "/api/v1/user"
body : Json.Encode.Value
body =
User.encode user
in
Command.request url Nothing body UserCommandExecuted
addUserCommandTask : User -> Task () UserResponse
addUserCommandTask user =
let
url : String
url = baseUrl ++ "/api/v1/user"
body : Json.Encode.Value
body =
User.encode user
in
Command.requestTask url Nothing body
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
import UUID exposing (UUID) -- elm install TSFoster/elm-uuid (for example)
import User exposing (User) -- An example module
type alias UserResponse =
Query.Response User
type Msg =
UserQueryExecuted UserResponse
baseUrl : String
baseUrl =
"https://example.com"
findUserQuery : Uuid -> Cmd Msg
findUserQuery id =
let
url : String
url = baseUrl ++ "/api/v1/user/" ++ Uuid.toString id
in
Query.request url Nothing UserQueryExecuted User.decoder
findUserQueryTask : Uuid -> Task () UserResponse
findUserQueryTask id =
let
url : String
url = baseUrl ++ "/api/v1/user/" ++ Uuid.toString id
in
Query.requestTask url Nothing User.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. If Nothing
is provided, a default error handler will be triggered to format the incoming HTTP error.
To create a custom http error handler, you need to implement a function which takes an Http.Error
and returns a String
. For example:
import Http exposing (Error(..)) -- elm install elm/http
myCustomHttpErrorHandler : Http.Error -> String
myCustomHttpErrorHandler error =
case error of
...
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
Query.request url (Just myCustomHttpErrorHandler) ...
Command.request url (Just myCustomHttpErrorHandler) ...