Created
January 11, 2016 08:05
-
-
Save alexbiehl/60b07a7f7a0074970fe6 to your computer and use it in GitHub Desktop.
Example of how to use protocol-buffers service definitions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
message SearchRequest { | |
required string query = 1; | |
optional int32 page_number = 2; | |
optional int32 result_per_page = 3; | |
} | |
message SearchResponse { | |
repeated string results = 1; | |
} | |
service SearchService { | |
rpc Search (SearchRequest) returns (SearchResponse); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- $ hprotoc search.proto | |
-- $ stach ghc SearchService.hs | |
{-# LANGUAGE DataKinds #-} | |
{-# LANGUAGE ExistentialQuantification #-} | |
{-# LANGUAGE KindSignatures #-} | |
{-# LANGUAGE ScopedTypeVariables #-} | |
{-# LANGUAGE ConstraintKinds #-} | |
{-# LANGUAGE GeneralizedNewtypeDeriving #-} | |
{-# LANGUAGE OverloadedStrings #-} | |
import Control.Monad.Reader | |
import Control.Concurrent | |
import Data.ByteString (ByteString) | |
import qualified Data.ByteString.Char8 as ByteString | |
import Data.Proxy | |
import GHC.TypeLits | |
import Text.ProtocolBuffers | |
-- The protobuf messages | |
import Search.SearchRequest | |
import Search.SearchResponse | |
-- This is defined in protocol-buffers library | |
data Method (name :: Symbol) req resp = Method | |
-- This is defined in protocol-buffers library | |
methodName :: forall name req resp . KnownSymbol name => Method name req resp -> String | |
methodName _ = symbolVal (Proxy :: Proxy name) | |
-- This will be generated from the service definitions | |
type Search = Method "search" SearchRequest SearchResponse | |
-- This will be generated from the service definitions | |
search :: Search | |
search = Method | |
-- This would be nice to have in protocol-buffers library | |
type MethodCxt name req resp = ( ReflectDescriptor req | |
, ReflectDescriptor resp | |
, Wire req | |
, Wire resp | |
, KnownSymbol name | |
) | |
-- Example | |
-- This is some rpc implementors connection definition. Currently a dummy here | |
data Connection = Connection | |
-- This is a monad an implementor wants to use | |
newtype RpcM m a = RpcM { unRpcM :: ReaderT Connection m a } | |
deriving ( Functor, Applicative, Monad | |
, MonadReader Connection, MonadIO | |
) | |
runRpcM :: Monad m => Connection -> RpcM m a -> m a | |
runRpcM conn (RpcM m) = runReaderT m conn | |
-- invoke reifies the Method type to value level. This is the adapter one has to | |
-- write in order use the generated method definitions. | |
invoke :: forall m name req resp . (MonadIO m, MethodCxt name req resp) => Method name req resp -> req -> RpcM m resp | |
invoke m = \req -> do | |
conn <- ask | |
res <- liftIO $ do | |
-- maybe an implementor wants the io to happen asynchronously. | |
-- Just to demonstrate anything is possible and our method definitions | |
-- are not constraining us in anything | |
mvar <- newEmptyMVar | |
invokeRaw conn method req (putMVar mvar) | |
takeMVar mvar | |
return res | |
-- needed to get the services method name | |
where method = ByteString.pack (methodName m) | |
-- invokeRaw takes the usual arguments one would expect from an rpc client. | |
-- Not much type level stuff in here! | |
invokeRaw :: (ReflectDescriptor req, ReflectDescriptor resp, Wire req, Wire resp) | |
=> Connection -> ByteString -> req -> (resp -> IO ()) -> IO () | |
invokeRaw conn method req respond = do | |
-- ... | |
-- do some connection reading stuff and deserialize/serialize action | |
-- ... | |
let result = undefined | |
-- return the result | |
respond result | |
main :: IO () | |
main = do | |
let conn = Connection | |
runRpcM conn $ do | |
-- invoke a search! | |
SearchResponse rx <- invoke search SearchRequest { query = Utf8 "hello world" | |
, page_number = Nothing | |
, result_per_page = Nothing | |
} | |
-- do something with SearchResponse | |
liftIO $ print rx |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment