Module
Tutorial.Nameservice.Module
At this point we can collect the relevant pieces to form the Nameservice module:
module Tutorial.Nameservice.Module where
import Nameservice.Modules.Nameservice.Keeper (NameserviceEffs, eval)
import Nameservice.Modules.Nameservice.Query (QueryApi, querier)
import Nameservice.Modules.Nameservice.Router (MessageApi, messageHandlers)
import Nameservice.Modules.Nameservice.Types (NameserviceName)
import Tendermint.SDK.Application (Module (..), ModuleEffs)
import Tendermint.SDK.BaseApp (DefaultCheckTx (..))
import Tendermint.SDK.Modules.Bank (Bank)
import Data.Proxy
import Polysemy (Members)
-- a convenient type alias
type Nameservice =
Module NameserviceName MessageApi MessageApi QueryApi NameserviceEffs '[Bank]
nameserviceModule
:: Members (ModuleEffs Nameservice) r
=> Nameservice r
nameserviceModule = Module
{ moduleTxDeliverer = messageHandlers
, moduleTxChecker = defaultCheckTx (Proxy :: Proxy MessageApi) (Proxy :: Proxy r)
, moduleQuerier = querier
, moduleEval = eval
}
Here we are using defaultCheckTx
as our transaction checker, which is a static, message validating handler defined as:
defaultCheckTxHandler
:: Member (Error AppError) r
=> ValidateMessage msg
=> RoutingTx msg
-> Sem r ()
defaultCheckTxHandler(RoutingTx Tx{txMsg}) =
case validateMessage txMsg of
V.Failure err ->
throwSDKError . MessageValidation . map formatMessageSemanticError $ err
V.Success _ -> pure ()
Note that this checker can be used to implement any transaction for which
- The message accepted by the router has a
ValidateMessage
instance - The return type in the serve type is
Return ()
To generate a server for which every transaction has these properties, we used the defaultCheckTx
type class method on the MessageApi
type. This will generate a server of type VoidReturn MessageApi
, which has the exact same shape as MessageApi
just with all the return values changed to Return ()
. In this particular case all handlers for MessageApi
already return ()
, so we have MessageApi ~ VoidReturn MessageApi
and there's no need to use the VoidReturn
family in the module type.
Note the constraint on r
in the Module's type using the constraint-valued type family ModuleEffs
. In this case it evaluates to the following equivalent set of constraints:
...
ModuleEffs Nameservice r
~ ( Members NameserviceEffs r
, Members (DependencyEffs '[Bank] r)
, Members TxEffs r
, Members BaseEffs r
)
~ ( Members NameserviceEffs r
, Members BankEffs r
, Members TxEffs r
, Members BaseEffs r
)
...
This is saying that we can run this module in any context for which r
has the effects from NameserviceEffs
, BankEffs
, TxEffs
, and BaseEffs
. This is how we explicitly declare global effect dependencies for a module, by using the constraint system.
Other than that, there is nothing really to note. We are just collecting the pieces we have already defined in one place.