mirror of
https://github.com/higherkindness/mu-haskell.git
synced 2024-09-11 14:36:01 +03:00
Fix some typos in the docs (#76)
This commit is contained in:
parent
65e93f2184
commit
ed98a2070e
@ -6,7 +6,7 @@ permalink: /
|
||||
|
||||
# Docs for Mu-Haskell
|
||||
|
||||
Mu-Haskell is a set of packages that help you build both servers and clients for (micro)services. The main goal of Mu-Haskell is to make you focus on your domain logic, instead of worrying about format and protocol issues.
|
||||
Mu-Haskell is a set of packages that help you build both servers and clients for (micro)services. The main goal of Mu-Haskell is to allow you to focus on your domain logic, instead of worrying about format and protocol issues.
|
||||
|
||||
* [Introduction]({% link docs/intro.md %})
|
||||
* [Schemas]({% link docs/schema.md %})
|
||||
|
@ -31,7 +31,7 @@ service PersistentService {
|
||||
|
||||
Maybe this example looks a bit contrived but bear with me, it covers a common use case when working with protobuf: that one of the messages has another message as its identifying key.
|
||||
|
||||
## Definning our Schema
|
||||
## Defining our Schema
|
||||
|
||||
You are going to need to enable the following extensions:
|
||||
|
||||
@ -119,13 +119,13 @@ deriving via (WithEntityNestedId "Person" PersonFieldMapping (Entity Person))
|
||||
instance ToSchema Maybe PersistentSchema "Person" (Entity Person)
|
||||
```
|
||||
|
||||
Have in mind that we still need to define our own custom field mapping, in this case `PersonFieldMapping` so that the deriving via does it's job properly.
|
||||
Have in mind that we still need to define our own custom field mapping, in this case `PersonFieldMapping` so that the deriving via does its job properly.
|
||||
|
||||
## Running a pool of database connections
|
||||
|
||||
Now let's focus on the Server!
|
||||
|
||||
All you need to do is open one time the database, and share the connection across all your services:
|
||||
All you need to do is open the database once, and share the connection across all your services:
|
||||
|
||||
```haskell
|
||||
{-# language FlexibleContexts #-}
|
||||
@ -147,7 +147,7 @@ main =
|
||||
liftIO $ runGRpcApp 8080 (server conn)
|
||||
```
|
||||
|
||||
We have decided in this example to use `LoggintT` from `monad-logger` and `runStderrLoggingT` to get some basic database logs to the console for free, but this is not a must!
|
||||
We have decided in this example to use `LoggingT` from `monad-logger` and `runStderrLoggingT` to get some basic database logs to the console for free, but this is not a must!
|
||||
|
||||
## This actually does not work
|
||||
|
||||
@ -191,7 +191,7 @@ allPeople conn sink = runDb conn $
|
||||
|
||||
As you can see, all the services need to be passed the `SqlBackend` connection as an argument.
|
||||
|
||||
Two interesting things we want to highlight here: we have provided a small helper called `runDb`, it's implementation is quite simple and it exists due to **developer ergonomics**. We are basically saving you from writing lots of `liftIO $ flip runSqlPersistM`. 😉
|
||||
Two interesting things we want to highlight here: we have provided a small helper called `runDb`, its implementation is quite simple and it exists due to **developer ergonomics**. We are basically saving you from writing lots of `liftIO $ flip runSqlPersistM`. 😉
|
||||
|
||||
The second one will be discussed in the next section.
|
||||
|
||||
@ -212,7 +212,7 @@ liftServerConduit
|
||||
=> ConduitT a b ServerErrorIO r -> ConduitT a b m r
|
||||
```
|
||||
|
||||
What is this type signature telling us? That is, we can turn any of the Conduits given as input, which work on the `ServerErrorIO` Monad from `mu-rpc`, into a Conduit working on other `IO`-like Monad. This is the case, in particular, of the Monad in which Persistent runs.
|
||||
What is this type signature telling us? That is, we can turn any of the Conduits given as input, which work on the `ServerErrorIO` Monad from `mu-rpc`, into a Conduit working on another `IO`-like Monad. This is the case, in particular, of the Monad in which Persistent runs.
|
||||
|
||||
|
||||
And that concludes our round-trip!
|
||||
|
@ -10,7 +10,7 @@ Mu-Haskell defines a generic notion of service and server that implements it. Th
|
||||
|
||||
## Running the server with `mu-grpc`
|
||||
|
||||
The combination of the declaration of a service API and a corresponding implementation as a `Server` may served directly using a concrete wire protocol. One example is gRPC, provided by our sibling library `mu-grpc`. The following line starts a server at port `8080`, where the service can be found under the package name `helloworld`:
|
||||
The combination of the declaration of a service API and a corresponding implementation as a `Server` may be served directly using a concrete wire protocol. One example is gRPC, provided by our sibling library `mu-grpc`. The following line starts a server at port `8080`, where the service can be found under the package name `helloworld`:
|
||||
|
||||
```haskell
|
||||
main = runGRpcApp 8080 "helloworld" quickstartServer
|
||||
@ -47,8 +47,6 @@ main = do
|
||||
|
||||
Where `watch`, `get` and `add` are the only valid 3 commands that our CLI is going to accept and call each respective service.
|
||||
|
||||
If you are not familiar with `TypeApplications`, you can check [this](https://www.reddit.com/r/haskell/comments/6ufnmr/scrap_your_proxy_arguments_with_typeapplications/), [that](https://blog.sumtypeofway.com/posts/fluent-polymorphism-type-applications.html) and [this](https://kseo.github.io/posts/2017-01-08-visible-type-application-ghc8.html).
|
||||
|
||||
### Using records
|
||||
|
||||
This option is a bit more verbose but it's also more explicit with the types and _"a bit more magic"_ than the one with `TypeApplications` (due to the use of Generics).
|
||||
@ -66,7 +64,7 @@ data Call = Call
|
||||
} deriving Generic
|
||||
```
|
||||
|
||||
Note that we had to derive `Generic`. We also need to tweak a little bit our `main` function:
|
||||
Note that we had to derive `Generic`. We also need to tweak our `main` function a little bit:
|
||||
|
||||
```diff
|
||||
main :: IO ()
|
||||
@ -113,6 +111,9 @@ watching client = do
|
||||
|
||||
With `TypeApplications` none of the above is needed, all you need to do is call `gRpcCall` with the appropiate service name as a type-level string, and the rest just _magically_ works! ✨
|
||||
|
||||
If you are not familiar with `TypeApplications`, you can check [this](https://www.reddit.com/r/haskell/comments/6ufnmr/scrap_your_proxy_arguments_with_typeapplications/), [that](https://blog.sumtypeofway.com/posts/fluent-polymorphism-type-applications.html) and [this](https://kseo.github.io/posts/2017-01-08-visible-type-application-ghc8.html).
|
||||
|
||||
|
||||
```haskell
|
||||
import Mu.GRpc.Client.TyApps
|
||||
|
||||
|
@ -6,16 +6,16 @@ permalink: intro/
|
||||
|
||||
# Introduction to Mu-Haskell
|
||||
|
||||
Many companies have embraced microservices architectures as the best way to scale up their internal software systems, separate work across different company divisions and development teams. Microservices architectures also allow teams to turn an idea or bug report into a working feature of fix in production more quickly, in accordance to the agile principles.
|
||||
Many companies have embraced microservices architectures as the best way to scale up their internal software systems, and separate work across different company divisions and development teams. Microservices architectures also allow teams to turn an idea or bug report into a working feature or fix in production more quickly, in accordance to the agile principles.
|
||||
|
||||
However, microservices are not without costs. Every connection between microservices becomes now a boundary that requires one to act as a server, and the other to act as the client. Each part implementation needs to add the protocol, the codification of the data for transmission, etc. Also, business logic of the application starts to spread around several code bases, making it difficult to maintain.
|
||||
However, microservices are not without costs. Every connection between microservices becomes now a boundary that requires one service to act as a server, and the other to act as the client. Each service needs to include an implementation of the protocol, the encoding of the data for transmission, etc. The business logic of the application also starts to spread around several code bases, making it difficult to maintain.
|
||||
|
||||
## What is Mu-Haskell?
|
||||
|
||||
The main goal of Mu-Haskell is to make you focus on your domain logic, instead of worrying about format and protocol issues. To achieve this goal, Mu-Haskell provides two sets of packages:
|
||||
The main goal of Mu-Haskell is to allow you to focus on your domain logic, instead of worrying about format and protocol issues. To achieve this goal, Mu-Haskell provides two sets of packages:
|
||||
|
||||
* `mu-schema` and `mu-rpc` define schemas for data and services, in a format- and protocol-independent way. These schemas are checked at compile-time, so you also gain an additional layer of type-safety.
|
||||
* `mu-avro`, `mu-protobuf`, `mu-grpc` (and other to come) implement each concrete format and protocol, following the interfaces laid out by the former two. In addition, most of those packages can turn a schema in the corresponding format into the corresponding one in `mu-schema` and `mu-rpc` terms, alleviating you from the need of duplicating definitions.
|
||||
* `mu-avro`, `mu-protobuf`, `mu-grpc` (and other to come) implement each concrete format and protocol, following the interfaces laid out by the former two. In addition, most of those packages can turn a schema in the corresponding format into the corresponding one in `mu-schema` and `mu-rpc` terms, alleviating the need to duplicate definitions.
|
||||
|
||||
## Quickstart
|
||||
|
||||
@ -42,10 +42,12 @@ message HelloReply { string message = 1; }
|
||||
To get started with the project, we provide a [Stack](https://docs.haskellstack.org) template (in fact, we recommend that you use Stack as your build tool, although Cabal should also work perfectly fine). You should run:
|
||||
|
||||
```
|
||||
stack new my-project https://raw.githubusercontent.com/higherkindness/mu-haskell/master/templates/grpc-server.hsfiles -p "author-email:your@email.com" -p "author-name:Your name"
|
||||
stack new my_project https://raw.githubusercontent.com/higherkindness/mu-haskell/master/templates/grpc-server.hsfiles -p "author-email:your@email.com" -p "author-name:Your name"
|
||||
```
|
||||
|
||||
This command creates a new folder called `my-project`, with a few files. The most important from those are the `.proto` file, in which you shall declare your service; `src/Schema.hs`, which loads the service definition at compile-time; and `src/Main.hs`, which contains the code of the server.
|
||||
**WARNING:** Do not include a hyphen in your project name, as it will cause the template to generate a '.proto' file containing an invalid package name. Use `my_project`, not `my-project`.
|
||||
|
||||
This command creates a new folder called `my_project`, with a few files. The most important from those are the `.proto` file, in which you will define your service; `src/Schema.hs`, which loads the service definition at compile-time; and `src/Main.hs`, which contains the code of the server.
|
||||
|
||||
The first step to get your project running is defining the right schema and service. In this case, you can just copy the definition above after the `package` declaration.
|
||||
|
||||
@ -59,26 +61,29 @@ The aforementioned `.proto` file defines two messages. The corresponding data ty
|
||||
data HelloRequestMessage
|
||||
= HelloRequestMessage { name :: Maybe T.Text }
|
||||
deriving (Eq, Show, Generic
|
||||
, ToSchema Maybe Schema "HelloRequest"
|
||||
, FromSchema Maybe Schema "HelloRequest")
|
||||
, ToSchema Maybe TheSchema "HelloRequest"
|
||||
, FromSchema Maybe TheSchema "HelloRequest")
|
||||
|
||||
data HelloReplyMessage
|
||||
= HelloReplyMessage { message :: Maybe T.Text }
|
||||
deriving (Eq, Show, Generic
|
||||
, ToSchema Maybe Schema "HelloReply",
|
||||
, FromSchema Maybe Schema "HelloReply")
|
||||
, ToSchema Maybe TheSchema "HelloReply"
|
||||
, FromSchema Maybe TheSchema "HelloReply")
|
||||
```
|
||||
|
||||
You can give those data types and their constructors any name you like. However, keep in mind that:
|
||||
These data types should be added to the file `src/Schema.hs`, under the line that starts `grpc ...`. (See the [gRPC page]({% link docs/grpc.md %}) for information about what that line is doing.)
|
||||
|
||||
You can give the data types and their constructors any name you like. However, keep in mind that:
|
||||
|
||||
* The names of the fields must correspond with those in the `.proto` files. Otherwise you have to use a *custom mapping*, which is fully supported by `mu-schema` but requires more code.
|
||||
* All the fields must be wrapped in `Maybe` since all fields in `proto3` are **optional by default**.
|
||||
* The name `TheSchema` refers to a type generated by the `grpc` function, so it must match the first argument to that function.
|
||||
* The name between quotes in each `deriving` clause defines the message type in the `.proto` file each data type corresponds to.
|
||||
* To use the automatic-mapping functionality, it is required to also derive `Generic`, don't forget it!
|
||||
|
||||
#### Server implementation
|
||||
|
||||
If you try to compile the project right now by means of `stack build`, you will receive an error about `server` not having the right type. This is because you haven't defined yet any implementation for your service. This is one of the advantages of making the compiler aware of your service definitions: if the `.proto` file changes, you need to adapt your code correspondingly, or otherwise the project doesn't even compile!
|
||||
If you try to compile the project right now by means of `stack build`, you will receive an error about `server` not having the right type. This is because you haven't yet defined any implementation for your service. This is one of the advantages of making the compiler aware of your service definitions: if the `.proto` file changes, you need to adapt your code correspondingly, or otherwise the project doesn't even compile!
|
||||
|
||||
Open the `src/Main.hs` file. The contents are quite small right now: a `main` function asks to run the gRPC service defined by `server`. The `server` function, on the other hand, declares that it implements the `Service` service in its signature, but contains no implementations.
|
||||
|
||||
@ -90,17 +95,17 @@ server :: (MonadServer m) => ServerT Maybe Service m _
|
||||
server = Server H0
|
||||
```
|
||||
|
||||
The simplest way to provide an implementation for a service is to define one function for each method. You define those functions completely in terms of Haskell data types; in our case `HelloRequestMessage` and `HelloReplyMessage`. Here is a simple definition:
|
||||
The simplest way to provide an implementation for a service is to define one function for each method. You define those functions completely in terms of Haskell data types; in our case `HelloRequestMessage` and `HelloReplyMessage`. Here is an example definition:
|
||||
|
||||
```haskell
|
||||
sayHello :: (MonadServer m) => HelloRequestMessage -> m HelloReplyMessage
|
||||
sayHello (HelloRequestMessage nm)
|
||||
= return $ HelloReplyMessage ("hello, " ++ nm)
|
||||
= return (HelloReplyMessage (("hi, " <>) <$> nm))
|
||||
```
|
||||
|
||||
The `MonadServer` portion in the type is mandated by `mu-rpc`; it tells us that in a method we can perform any `IO` actions and additionally throw server errors (for conditions such as *not found*). We do not make use of any of those here, so we simply use `return` with a value. We could even make the definition a bit more polymorphic by replacing `MonadServer` by `Monad`.
|
||||
|
||||
How does `server` know that `sayHello` is part of the implementation of the service? We have to tell it, by adding `sayHello` to the list of methods. Unfortunately, we cannot use a simple lists, so we use `(:<|>:)` to join them, and `H0` to finish it.
|
||||
How does `server` know that `sayHello` is part of the implementation of the service? We have to tell it, by adding `sayHello` to the list of methods. Unfortunately, we cannot use a normal list, so we use `(:<|>:)` to join them, and `H0` to finish it.
|
||||
|
||||
```haskell
|
||||
server = Server (sayHello :<|>: H0)
|
||||
|
@ -43,7 +43,7 @@ This is everything you need to start using gRPC services and clients in Haskell!
|
||||
|
||||
### Looking at the resulting code
|
||||
|
||||
In order to use the library proficiently, we should look a bit at the code generated in the previous code. A type-level description of the messages is put into the type `QuickstartSchema`. However, there is some code you still have to write by hand, namely the Haskell type which correspond to that schema. Using `mu-schema` facilities, this amounts to declaring a bunch of data types and including `deriving (Generic, ToSchema Maybe Schema "type", FromSchema Maybe Schema "type")` at the end of each of them.
|
||||
In order to use the library proficiently, we should look a bit at the code generated in the previous sample. A type-level description of the messages is put into the type `QuickstartSchema`. However, there is some code you still have to write by hand, namely the Haskell type which correspond to that schema. Using `mu-schema` facilities, this amounts to declaring a bunch of data types and including `deriving (Generic, ToSchema Maybe <SchemaName> "<MessageType>", FromSchema Maybe <SchemaName> "<MessageType>")` at the end of each of them.
|
||||
|
||||
```haskell
|
||||
{-# language PolyKinds, DataKinds, TypeFamilies #-}
|
||||
@ -78,7 +78,7 @@ newtype HelloResponse
|
||||
, FromSchema Maybe QuickstartSchema "HelloResponse")
|
||||
```
|
||||
|
||||
The service declaration looks very similar to an schema declaration, but instead of record and enumerations you define *methods*. Each method has a name, a list of arguments, and a return type.
|
||||
The service declaration looks very similar to a schema declaration, but instead of records and enumerations you define *methods*. Each method has a name, a list of arguments, and a return type.
|
||||
|
||||
```haskell
|
||||
import Mu.Rpc
|
||||
@ -91,7 +91,7 @@ type QuickstartService
|
||||
('RetSingle ('FromSchema QuickstartSchema "HelloResponse")) ]
|
||||
```
|
||||
|
||||
In order to support both [Avro IDL](https://avro.apache.org/docs/current/idl.html) and [gRPC](https://grpc.io/), the declaration of the method arguments and returns in a bit fancier that you might expect:
|
||||
In order to support both [Avro IDL](https://avro.apache.org/docs/current/idl.html) and [gRPC](https://grpc.io/), the declaration of the method arguments and return types is a bit fancier than you might expect:
|
||||
|
||||
* Each *argument* declares the schema type used for serialization. Furthermore, the argument can be declared as `ArgSingle` (only one value is provided by the client) or `ArgStream` (a stream of values is provided).
|
||||
* The *return types* gives the same two choices under the names `RetSingle` or `RetStream`, and additionally supports the declaration of methods which may raise exceptions using `RetThrows`, or methods which do not retun any useful information using `RetNothing`.
|
||||
|
@ -102,7 +102,7 @@ Once again, you need to enable some extensions in the compiler (but do not worry
|
||||
|
||||
## Customizing the mapping
|
||||
|
||||
Sometimes the names of the fields in the Haskell data type and the names of the fields in the schema do not match. For example, in our schema above we use `male`, `female`, and `nb`, but in a Haskell enumeration the name of each constructor must begin with a capital letter. By using a stand-along `ToSchema` instance you can declare a custom mapping from Haskell fields or constructors to schema fields or enum choices, respectively:
|
||||
Sometimes the names of the fields in the Haskell data type and the names of the fields in the schema do not match. For example, in our schema above we use `male`, `female`, and `nb`, but in a Haskell enumeration the name of each constructor must begin with a capital letter. By using a standalone `ToSchema` instance you can declare a custom mapping from Haskell fields or constructors to schema fields or enum choices, respectively:
|
||||
|
||||
```haskell
|
||||
{-# language DerivingVia #-}
|
||||
|
@ -96,6 +96,7 @@ grpc "TheSchema" id "{{name}}.proto"
|
||||
{-# START_FILE src/Main.hs #-}
|
||||
{-# language FlexibleContexts #-}
|
||||
{-# language PartialTypeSignatures #-}
|
||||
{-# language OverloadedStrings #-}
|
||||
{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}
|
||||
module Main where
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user