I've been struggling to come up with a good patter...
# random
t
I've been struggling to come up with a good pattern for DDD and supporting a public GraphQL API. Wanted to ask you guys what you think: For a public GQL api, you want to support a large set of queries because you don't know what exactly they might need. How do you structure your code to allow really flexible querying into your business logic? For example if you take the simple Blog example you might have Author, Post, Comments, Commenter I might have a domain called
Post
with functions like
Post.fromAuthor(author)
But it's possible the query is asking for posts for a specific author, the comments, and the first name of each commenter. That turns into
Post.fromAuthorWithCommentsWithCommenterFirstName()
Of course there's a lot of ways to simplify this, for example using a builder pattern, but how do people think about this general situation? I don't want to write build a sql statement in my API layer because I want to create seperation so refactoring database details don't require API changes...but makes me wonder if DDD principles even map well to serverless
cc @Omi Chowdhury
You can rely on nested resolvers but it seems common that those will be inefficient since they'll be doing application level joins
d
Copy code
query {
	profileOrganisations(first:5, filter: { profileSlug__eq: "lvmh" }) {
		edges {
			node {
				_id
        title
        keyPeople(first: 2, filter:{
          profileRelationTypeId__eq:1
        }){
					edges{
            node{
              _id
              title
            }
          }
        }
			}
		}
	}
}
We did this and it’s super flexible yet allows to retain control
o
I use nested resolvers, along with dataloader, reading off dynamo or algolia - so joins aren’t really a thing. Between batching and caching, it’s working out alright. Our queries are usually not deep though, I think if I implemented a 5 depth limit on our API, none of our customers would notice
d
Yep, data loader is a must. So if you query posts with author details, it would be two queries, one for posts, and one for author data for all posts, and then it’s a server side ‘join’
This worked really well for us with DDD.
The only potential issue would be if we’d have a filter called ‘query’ where it would read from Algolia .. and then you get into an issue where you need to support all other filter types potentially … but at that point it would just be a different searchProfileOrganisations node.
t
so what is the exact code architecture for this. Are you talking to your DBs straight inside your gql resolver code? Or do you create a middle layer that abstracts those details
I kinda wish I can just poke around a mature codebase
d
It’s PHP code .. we have CQRS so a layer between a repository and DB
It would be something like ProfileQueryService with caching etc
t
So how do you expose functions on ProfileQueryService while supporting all the different things the query could be asking for? Do you always just return everything and let the resolver filter down to what it needs?
d
No, it gets passed down (the fields) that are needed. But query service has one method (query) so that’s why the filter input was so great for us as that’s basically the args for the query service which then figures out what to do with that
And with filter input you define exactly what you support .. so it’s not ‘all the different things’
t
Right I'm doing something similar as well where there's a single
opts
object that contains all the ways you can filter or ask for nested data. It all doesn't feel just right though
someone shared this with me today: https://github.com/join-monster/join-monster Couples API a lot with a relational data source but I'm now wondering if this maybe is ok
I'm imagining something like this where you can define an
operation
for each field and once it figures out all the operations it needs to do, it can intelligently combine them to execute them efficiently
d
Graphql node can be rest, redis, algolia, mongo .. whatever .. with all of those is a bigger project .. so to me bringing data access details anywhere near graphql schema is not ok :)
But the beauty of graphql is that if you design the schema well you can tinker with data access in the future, when db server is really on fire
Until then it might be a problem not worth solving
t
Well I'm thinking more about using my database as much as I can instead of doing application level joins. Eg if someone asks for Post(1) -> Author -> Friends, I want that to be resolved in the database and not in my application as seperate steps
Writing clean code, easy to maintain code, feels like it results in a lot of application level joins
d
It can’t be, it will need to be 3 queries minimum. But that’s it .. what the join monster does but a bit more separated code wise
What we did early on, which was a total overkill… we put every single domain’s tables in a seperate db where developers were’t able to join in db
And then they got it and implemented this correctly 😅
t
yeah meditated on it more and I think you both are right, I shouldn't give up the decoupling of db and gql - knew that was bad in my gut So are you both using AppSync? With the nested resolver framework they provide? Sounds like maybe no since you're using dataloader directly
o
I’m using apollo server and lambda - along with graphql-shield for authorisation. I sense that AppSync has come a long way since I had to make my appsync vs apollo decision - but I don’t know if I could do a lot of the things I do in my API with appsync
Maybe I probably could, but I’m always wary whenever AWS invents a DSL to solve some problem 😅
t
AppSync still has a 5 item limit on batch invoke lol. That means if you fetch 20 posts then go to get their authors, you're potentially doing 4 separate invokes
Idk what they're thinking with that, feels like it nerfs the entire thing
I wanted to use AppSync because of the GQL subscription support but that feels like a deal breaker
o
I think an SST construct that uses a websocket API gateway and dynamo for subscriptions would be killer - I think I saw someone post an example for it
t
AppSync also has no way to trigger serverside subscriptions besides a really annoying workaround. So right now leaning towards Apollo server as well. Are you using subscriptions at all? Wonder how much of a pain it is to get it going with API gateway websocket
o
Not using subscriptions - been on my list of things to explore
t
Do you guys implement dataloader at the api layer or more inside of your domains?
o
Inside the data layer, have a thin wrapper around dynamodb-toolbox, for example
t
Are you initializing dataloader just once then? Or do you use per request? And if it's just once are you disabling caching?
o
do it per request - when we create the context object on in apollo
t
Cool 👍🏽 don't you then have to be aware of all the different kinds of dataloaders that all of your data layer is using? Or is there somehow a way to just initialize once and reuse it for everything
o
We have two dataloaders - for dynamo and qldb. Algolia only gets used in a few cases, so wouldn’t benefit from a loader