This is a summary of how 99d’s infrastructure works holistically for a single component that fires a request when a button is pressed. The UI I’m using in this example is <DismissJobButton />
that is defined within the pages/browse-projects
directory. Let’s walk through the steps of what happens:
-
When the component renders we invoke Apollo Clients
useMutation
hook which returns a function that will fireonSubmit
when the dismiss button is pressed -
Note here that we don't directly invoke useMutation, instead we use a library called GraphQL Code Generator to generate custom hooks, the command used to generate this code is:
nx run spa:graphql
When exactly can you run this generator?
The generator looks for the schema files in backend, so the type generation can be run after updating the .graphql files! See here.
-
The GraphQL mutation for dismissing a job sits alongside the component, this code strongly types the input that is passed to the mutation and specifies what values are returned from the mutation
-
The mutation input is sent to a resolver in our BFF (Backend For Frontend), this is the
backend
directory that is contained within thefrontend
repo, the code written within the backend defines a GraphQL schema and defines resolvers for mutations and queries that are then written inbackend/graph
, for exampleDismissJob
is a function that we define injob.go
and fires off a request to theDismissJob
RPC, this is an example of that call
func (m *mutationResolver) DismissJob(ctx context.Context, input models.DismissJobInput) (*createpb.JobForDesigner, error) {
res, err := m.Clients.Job.DismissJob(ctx, &createpb.DismissJobRequest{
Actor: actorFromContext(ctx),
JobId: input.ID,
Reasons: input.Reasons,
})
if err != nil {
return nil, err
}
return res.Job, nil
}
- If the resolvers are defined you can use gqlgen to turn your GraphQL types, mutations and queries into go structs, functions and interfaces, this ends up in
generated.go
, the command used to generate this code is
go generate ./...
Where are models defined?
gqlgen
will generate models for the types defined in the graphql schema files. For example, the input
that is passed to the mutation resolver is defined in the backend/models
directory in models_gen.go
. See code here. There is also some config around using the @goModel
directive but will come back to this another time.
What is the code in generated.go is used for?
This file contains the entire GraphQL server code that can used to attach to an endpoint. You can see it being configured here.
-
Before I mentioned the RPC function being invoked in the BFF however we also need to define this, the RPC framework we use is called Twirp, in this instance we're creating new jobs for designers (or dismissing jobs), therefore this RPC interface lives within the create service
-
Within the
rpc/proto
directory we have ajob.proto
file, this file defines a DismissJob RPC
rpc DismissJob(DismissJobRequest) returns (DismissJobResponse);
and also maps out what parameters a request can receive and a response can send.
- We then have some Go server code in the create service that needs to align to this RPC definition, this is defined in
rpc/job
and there is ajob.go
file with instance methods for the Service struct, one of which isDismissJob
func (s *Service) DismissJob(ctx context.Context, req *createpb.DismissJobRequest) (*createpb.DismissJobResponse, error) {
job, err := s.addJobInterest(ctx, model.NewJobInterestParams{
EffectiveUserID: int(req.Actor.EffectiveUserId),
RealUserID: int(req.Actor.RealUserId),
JobID: req.JobId,
Type: model.JobInterestTypeDismiss,
Data: castSdkDismissJobDataToDismissJobData(req.Reasons),
}, req.Actor)
if err != nil {
return nil, err
}
return &createpb.DismissJobResponse{Job: castJobForDesignerToSdk(job)}, nil
}
this function takes in parameters from the request and calls a function which executes some SQL and updates the DB, we then send back a response that contains the values we defined as a message in the RPC.
How does the mapper and SQL calls actually work?
It might be good to familiarize yourself with how to perform DB queries in Go.
- If there are no errors the resolver returns a job, that job is currently not being used in the
<DismissJob />
component