Skip to content

Commit

Permalink
Refactoring clean up (#788)
Browse files Browse the repository at this point in the history
* cache

* setup

* build cache

* buildCache

* Batching

* ResolverMapContext

* Batched

* with cache context

* resolve

* deriving

* cspell

* cspell

* batching tests

* update

* toONe

* batching

* Batching

* cache

* getSchema

* batching

* test batching

* fix batching tests

* batching

* optimize named resolvers

* resolveRef

use batched

post

resolveNamed

pages

beautify

deities

* update docs

* fix batching comparison

* fix formating
nalchevanidze authored Nov 5, 2022
1 parent 8c65056 commit 07b6916
Showing 73 changed files with 959 additions and 576 deletions.
1 change: 1 addition & 0 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ jobs:
run: |
stack install --fast morpheus-graphql-code-gen
morpheus check examples/code-gen
morpheus check examples/code-gen-docs
hlint:
runs-on: ubuntu-latest
115 changes: 78 additions & 37 deletions docs/pages/server.mdx
Original file line number Diff line number Diff line change
@@ -506,17 +506,20 @@ resolveDeity DeityArgs { } = throwError ("db error" :: GQLError)
Morpheus GraphQL provides two way of type resolving.

1. **Values as resolvers**: In this approach, you specify values
for the type definitions, where the resolvers are regular functions.
2. **Named resolvers**: In this approach, we use the type class `ResolveNamed` to define the
resolver for each type. More information on this approach can be
found in the next section.
for the type definitions, where the resolvers are regular functions.
However, this approach can sometimes lead to the possibility of overwhelming
the database with a huge amount of queries.
For this reason, the second approach, which involves automatic batching,
might be the more suitable solution for you.

<Section id="named-resolvers" level={2}>
Named Resolvers
</Section>
2. **Named resolvers**: In this approach, we use the type class `ResolveNamed` to define the
resolver function for each type that handles a batched list of dependencies.
More information on this approach can be found in the next section.

<Section id="named-resolvers" level={2}> Named Resolvers (Batching)</Section>

As mentioned earlier, in this approach we use `ResolveNamed`
to define the resolver function for each type. In this resolver definition,
to define the resolver function (with batching) for each type. In this resolver definition,
each type also defines its dependency (identifier), which is used by the
compiler to provide a corresponding output resolution for certain input values.
That is, if we want to resolve a type as a field of another type, we must
@@ -555,13 +558,19 @@ starting with type `Post`. The following instance specifies that for each unique
we can resolve the corresponding `Post`, where the post `title` is retrieved by the post `ID`.

```haskell
instance Monad m => ResolveNamed m (Post (NamedResolverT m)) where
getPostTitles :: Monad m => [ID] -> m [Maybe Text]
getPostTitles = <your db query>

resolvePosts :: Monad m => [ID] -> m [Maybe (Post (NamedResolverT m))]
resolvePosts ids = do
titles <- getPostTitles ids
pure (map (fmap toPost) titles)
where
toPost text = Post {title = lift (pure text)}

instance ResolveNamed m (Post (NamedResolverT m)) where
type Dep (Post (NamedResolverT m)) = ID
resolveNamed uid =
pure
Post
{ title = resolve (getPostTitleById uid)
}
resolveBatched = resolvePosts
```

Let's go to the next step and define a query resolver. Since the query does not
@@ -573,14 +582,19 @@ corresponding `Post` values by calling the `ResolveNamed`
instance of the type `Post` with those ids.

```haskell
instance Monad m => ResolveNamed m (Query (NamedResolverT m)) where
allPostIds :: m [ID]
allPostIds = <your db query>

resolveQuery :: Monad m => Query (NamedResolverT m)
resolveQuery =
Query
{ posts = resolve allPostIds,
post = \(Arg arg) -> resolve (pure arg)
}

instance ResolveNamed m (Query (NamedResolverT m)) where
type Dep (Query (NamedResolverT m)) = ()
resolveNamed () =
pure
Query
{ posts = resolve getPostIds,
post = \(Arg arg) -> resolve (pure (Just arg))
}
resolveBatched = pure . map (const $ Just resolveQuery)
```

In the last step, we can derive the GraphQL application using
@@ -634,18 +648,39 @@ As you can see, we can query `authors`, with each `Author` having their fields `
in the same manner as before, we can also provide their resolver implementation.

```haskell
instance Monad m => ResolveNamed m (Author (NamedResolverT m)) where
getAuthorPostIds :: Monad m => [ID] -> m [[ID]]
getAuthorPostIds = <your db query>

getAuthorNames :: Monad m => [ID] -> m [Text]
getAuthorNames = <your db query>

getAuthors :: Monad m => [ID] -> m [Maybe (Author (NamedResolverT m))]
getAuthors ids = do
postIds <- getAuthorPostIds ids
names <- getAuthorNames ids
pure (zipWith toAuthor names postIds)

toAuthor :: Monad m => Text -> [ID] -> Maybe (Author (NamedResolverT m))
toAuthor authorName postId =
Just
Author
{ name = lift (pure authorName),
posts = resolve (pure postId)
}

instance ResolveNamed m (Author (NamedResolverT m)) where
type Dep (Author (NamedResolverT m)) = ID
resolveNamed uid =
pure
Author
{ name = resolve (getAuthorName uid),
posts = resolve (getAuthorPosts uid)
}

instance Monad m => ResolveNamed m (Query (NamedResolverT m)) where
resolveBatched = getAuthors

getAllAuthorIds :: Monad m => m [ID]
getAllAuthorIds = undefined

resolveQuery :: Monad m => Query (NamedResolverT m)
resolveQuery = Query {authors = resolve getAllAuthorIds}

instance ResolveNamed m (Query (NamedResolverT m)) where
type Dep (Query (NamedResolverT m)) = ()
resolveNamed () = pure Query { authors = resolve getAuthorIds }
resolveBatched = pure . map (const $ Just resolveQuery)
```

At this stage, we have already implemented Authors and Query and now we can also
@@ -673,13 +708,19 @@ argument of the function does not match, one of the implementations
will be unable to decode the argument during resolution and it will fail.

```haskell
instance Monad m => ResolveNamed m (Post (NamedResolverT m)) where
getPostAuthorIds :: Monad m => [ID] -> m [ID]
getPostAuthorIds = <your db query>

resolvePosts :: Monad m => [ID] -> m [Maybe (Post (NamedResolverT m))]
resolvePosts ids = do
autors <- getPostAuthorIds ids
pure (map toPost autors)
where
toPost autorId = Just $ Post {author = resolve (pure autorId)}

instance ResolveNamed m (Post (NamedResolverT m)) where
type Dep (Post (NamedResolverT m)) = ID
resolveNamed uid =
pure
Post
{ author = resolve (pure uid)
}
resolveBatched = resolvePosts
```

Since all resolvers are implemented, we can also derive the application.
59 changes: 34 additions & 25 deletions examples/scotty/src/Server/NamedResolvers/Authors.hs
Original file line number Diff line number Diff line change
@@ -24,12 +24,11 @@ import Data.Morpheus.NamedResolvers
import Data.Morpheus.Types
( App,
Arg (..),
GQLError,
GQLType (..),
ID,
MonadError,
NamedResolvers (..),
Undefined,
lift,
)
import GHC.Generics (Generic)

@@ -42,10 +41,12 @@ data Role
ToJSON
)

instance Monad m => ResolveNamed m Role where
instance ResolveNamed m Role where
type Dep Role = ID
resolveNamed "1325" = pure Admin
resolveNamed _ = pure Guest
resolveBatched = traverse getRole
where
getRole "1325" = pure (Just Admin)
getRole _ = pure (Just Guest)

-- AUTHOR
data Author m = Author
@@ -58,15 +59,18 @@ data Author m = Author
GQLType
)

instance Monad m => ResolveNamed m (Author (NamedResolverT m)) where
instance ResolveNamed m (Author (NamedResolverT m)) where
type Dep (Author (NamedResolverT m)) = ID
resolveNamed uid =
pure
Author
{ authorId = resolve (pure uid),
role = resolve (pure uid),
posts = resolve (pure ["2321", "2112"])
}
resolveBatched = traverse getAuthor
where
getAuthor uid =
pure $
Just
Author
{ authorId = lift (pure uid),
role = resolve (pure uid),
posts = resolve (pure ["2321", "2112"])
}

-- POST EXTENSION
newtype Post m = Post
@@ -77,13 +81,16 @@ newtype Post m = Post
GQLType
)

instance Monad m => ResolveNamed m (Post (NamedResolverT m)) where
instance ResolveNamed m (Post (NamedResolverT m)) where
type Dep (Post (NamedResolverT m)) = ID
resolveNamed uid =
pure
Post
{ author = resolve (pure uid)
}
resolveBatched = traverse getPost
where
getPost uid =
pure $
Just
Post
{ author = resolve (pure uid)
}

-- QUERY
data Query m = Query
@@ -95,14 +102,16 @@ data Query m = Query
GQLType
)

instance MonadError GQLError m => ResolveNamed m (Query (NamedResolverT m)) where
instance ResolveNamed m (Query (NamedResolverT m)) where
type Dep (Query (NamedResolverT m)) = ()
resolveNamed () =
resolveBatched _ =
pure
Query
{ authors = resolve (pure ["1325", "2525"]),
authorById = \(Arg uid) -> resolve (pure (Just uid))
}
[ Just
Query
{ authors = resolve (pure ["1325", "2525"]),
authorById = \(Arg uid) -> resolve (pure uid)
}
]

authorsApp :: App () IO
authorsApp =
23 changes: 14 additions & 9 deletions examples/scotty/src/Server/NamedResolvers/Pages.hs
Original file line number Diff line number Diff line change
@@ -40,11 +40,14 @@ data Page m
GQLType
)

instance Monad m => ResolveNamed m (Page (NamedResolverT m)) where
getPage :: (Monad m) => ID -> m (Page (NamedResolverT m))
getPage "1325" = pure $ PagePost (resolve $ pure "1325")
getPage "2525" = pure $ PagePost (resolve $ pure "2525")
getPage x = pure $ PageAuthor (resolve $ pure x)

instance ResolveNamed m (Page (NamedResolverT m)) where
type Dep (Page (NamedResolverT m)) = ID
resolveNamed "1325" = pure $ PagePost (resolve $ pure "1325")
resolveNamed "2525" = pure $ PagePost (resolve $ pure "2525")
resolveNamed x = pure $ PageAuthor (resolve $ pure x)
resolveBatched = traverse (fmap Just . getPage)

-- QUERY
data Query m = Query
@@ -58,12 +61,14 @@ data Query m = Query

instance MonadError GQLError m => ResolveNamed m (Query (NamedResolverT m)) where
type Dep (Query (NamedResolverT m)) = ()
resolveNamed () =
resolveBatched _ =
pure
Query
{ pages = resolve (pure ["1325", "2415"]),
pageById = \(Arg uid) -> resolve (pure (Just uid))
}
[ Just
Query
{ pages = resolve (pure ["1325", "2415"]),
pageById = \(Arg uid) -> resolve (pure uid)
}
]

pagesApp :: App () IO
pagesApp =
29 changes: 18 additions & 11 deletions examples/scotty/src/Server/NamedResolvers/Posts.hs
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ import Data.Morpheus.Types
MonadError,
NamedResolvers (..),
Undefined,
lift,
)
import Data.Semigroup (Semigroup ((<>)))
import Data.Text (Text)
@@ -39,15 +40,19 @@ data Post m = Post
}
deriving (Generic, GQLType)

instance Monad m => ResolveNamed m (Post (NamedResolverT m)) where
type Dep (Post (NamedResolverT m)) = ID
resolveNamed pid =
pure
getPost :: (Monad m) => ID -> m (Maybe (Post (NamedResolverT m)))
getPost pid =
pure $
Just $
Post
{ postID = resolve (pure pid),
title = resolve (pure $ "title for \"" <> unpackID pid <> "\"")
{ postID = lift (pure pid),
title = lift (pure $ "title for \"" <> unpackID pid <> "\"")
}

instance ResolveNamed m (Post (NamedResolverT m)) where
type Dep (Post (NamedResolverT m)) = ID
resolveBatched = traverse getPost

-- QUERY
data Query m = Query
{ posts :: m [Post m],
@@ -60,12 +65,14 @@ data Query m = Query

instance MonadError GQLError m => ResolveNamed m (Query (NamedResolverT m)) where
type Dep (Query (NamedResolverT m)) = ()
resolveNamed () =
resolveBatched _ =
pure
Query
{ posts = resolve (pure ["1325", "2525"]),
post = \(Arg arg) -> resolve (pure (Just arg))
}
[ Just
Query
{ posts = resolve (pure ["1325", "2525"]),
post = \(Arg arg) -> resolve (pure arg)
}
]

postsApp :: App () IO
postsApp =
14 changes: 11 additions & 3 deletions morpheus-graphql-app/morpheus-graphql-app.cabal
Original file line number Diff line number Diff line change
@@ -42,8 +42,10 @@ data-files:
test/api/validation/input-coercion/list/list-single/query.gql
test/api/validation/input-coercion/list/schema.gql
test/api/validation/input-coercion/list/signle-value/query.gql
test/batching/deities.gql
test/batching/deities/query.gql
test/batching/object-lists/query.gql
test/batching/objects-fields/query.gql
test/batching/objects-lists-fields/query.gql
test/batching/schema.gql
test/merge/schema/query-subscription-mutation/app-1.gql
test/merge/schema/query-subscription-mutation/app-2.gql
test/merge/schema/query-subscription-mutation/mutation/query.gql
@@ -88,7 +90,12 @@ data-files:
test/api/validation/input-coercion/list/list-single/response.json
test/api/validation/input-coercion/list/resolvers.json
test/api/validation/input-coercion/list/signle-value/response.json
test/batching/deities/response.json
test/batching/object-lists/batching.json
test/batching/object-lists/response.json
test/batching/objects-fields/batching.json
test/batching/objects-fields/response.json
test/batching/objects-lists-fields/batching.json
test/batching/objects-lists-fields/response.json
test/merge/schema/query-subscription-mutation/app-1.json
test/merge/schema/query-subscription-mutation/app-2.json
test/merge/schema/query-subscription-mutation/mutation/response.json
@@ -120,6 +127,7 @@ library
Data.Morpheus.App.NamedResolvers
Data.Morpheus.Types.GQLWrapper
other-modules:
Data.Morpheus.App.Internal.Resolving.Batching
Data.Morpheus.App.Internal.Resolving.Event
Data.Morpheus.App.Internal.Resolving.Resolver
Data.Morpheus.App.Internal.Resolving.ResolverState
Loading

0 comments on commit 07b6916

Please sign in to comment.