Application
From Modules to App
The App
type in Network.ABCI.Server
is defined as
newtype App m = App
{ unApp :: forall (t :: MessageType). Request t -> m (Response t) }
and ultimately our configuration of modules must be converted to this format. This is probably the most important part of the SDK, to provide the bridge between the list of modules - a heterogeneous list of type Modules
- and the actual application. The type that provides the input for this bridge is HandlersContext
:
data HandlersContext alg ms r core = HandlersContext
{ signatureAlgP :: Proxy alg
, modules :: M.Modules ms r
, compileToCore :: forall a. ScopedEff core a -> Sem core a
}
where
alg
is the signature schema you would like to use for authentication (e.g. Secp256k1)ms
is the type level list of modulesr
is the global effects list for the applicationcore
is the set of core effects that are used to interpetBaseApp
toIO
.
We should say a few words on this compileToCore
field. The application developer has access to any effects in BaseApp
,
but BaseApp
itself still needs to be interpreted in order to run the application. In other words, BaseApp
is still just a
list of free effects. The set of effects capable of interpreting BaseApp
is called core
, and while the developer is free to provide any core
they want, we have a standard set of them in the SDK - e.g. in memory, production, etc.
The ScopedEff
type is more complicated and not relevant to the discussion of application development. Long story short, tendermint core requests three connections to the application's state - Consensus
, Mempool
and Query
. The ScopedEff
type is used to abstract this concern away from the developer, and as long as you are using one of the core
effects provided in the SDK you don't need to worry about it.
Tutorial.Nameservice.Application
module Tutorial.Nameservice.Application where
import Data.Proxy
import Nameservice.Modules.Nameservice (nameserviceModule, NameserviceM, NameserviceEffs)
import Nameservice.Modules.Token (tokenModule, TokenM, TokenEffs)
import Network.ABCI.Server.App (App)
import Polysemy (Sem)
import Tendermint.SDK.Modules.Auth (authModule, AuthEffs, AuthM)
import Tendermint.SDK.Application (Modules(..), HandlersContext(..), baseAppAnteHandler, makeApp)
import Tendermint.SDK.BaseApp (BaseApp, CoreEffs, (:&), compileScopedEff)
import Tendermint.SDK.Crypto (Secp256k1)
This is the part of the application where the effects list must be given a monomorphic type. There is also a requirement
that the Modules
type for the application be given the same order as the effects introducted. This ordering problem is due
to the fact that type level lists are used to represent the effects in polysemy
, and the order matters there. Still, it's only a small annoyance.
type EffR =
NameserviceEffs :&
TokenEffs :&
AuthEffs :&
BaseApp CoreEffs
type NameserviceModules =
'[ NameserviceM EffR
, TokenM EffR
, AuthM EffR
]
Notice that we've specified EffR
as the effects list for each of the modules to run in, which trivially satisfies the constraints on each module at the definition site, since it is simply the union of all effects.
We're now ready to define the HandlersContext
for our application:
handlersContext :: HandlersContext Secp256k1 NameserviceModules EffR CoreEffs
handlersContext = HandlersContext
{ signatureAlgP = Proxy @Secp256k1
, modules = nameserviceModules
, compileToCore = compileScopedEff
, anteHandler = baseAppAnteHandler
}
where
nameserviceModules :: Modules NameserviceModules EffR
nameserviceModules =
nameserviceModule
:+ tokenModule
:+ authModule
:+ NilModules
Finally we're able to define our application that runs in the CoreEffs
context defined in the SDK:
app :: App (Sem CoreEffs)
app = makeApp handlersContext