Skip to content

Instantly share code, notes, and snippets.

@alexbiehl
Created January 11, 2016 08:05
Show Gist options
  • Save alexbiehl/60b07a7f7a0074970fe6 to your computer and use it in GitHub Desktop.
Save alexbiehl/60b07a7f7a0074970fe6 to your computer and use it in GitHub Desktop.
Example of how to use protocol-buffers service definitions
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);
}
-- $ 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