Practical Guide to Getting Started with Spring Boot and Netflix DGS

Truong Bui
6 min readJul 7, 2024

--

Recently, in my self-directed learning journey, GraphQL and Netflix DGS caught my interest, prompting me to plan to write an article about it.

According to the official documentation:

GraphQL is a query language for your API, and a server-side runtime for executing queries using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.

The DGS framework makes it easy to create GraphQL services with Spring Boot. The framework provides an easy-to-use annotation-based programming model and all the advanced features needed to build and run GraphQL services at scale.

In this article, we’ll get started with GraphQL and Netflix DGS by building a Spring Boot project that integrates both technologies. Key points about this article include:

  1. Request Processing Journey
  2. Database diagram
  3. Scenarios
  4. Hands on

Request Processing Journey

Users interact with a web application to send requests to the server, typically in the form of GraphQL queries and mutations. The GraphQL server receives these requests, interprets the queries, and resolves them using the defined logic.

The server then communicates with the repository layer, which acts as an intermediary, handling the details of data storage and retrieval. The repository layer interacts with the H2 database to execute operations such as fetching, inserting, and updating data.

Once the database operations are complete, the repository processes the results and sends them back to the users.

Database diagram

  1. Customer table has One-to-Many relationship with Purchase table.
  2. Item table has One-to-Many relationship with Purchase table.
  3. Shop table has One-to-Many relationship with Item table.

Please note that this example demonstrates a simple e-commerce database schema, whereas a real-world schema would be much more complex.

Scenarios

The application supports the following scenarios:

  • Shop: Create new shops, retrieve a shop by ID, or retrieve all shops.
  • Item: Create new items, retrieve an item by ID, or retrieve items by shop ID.
  • Customer: Create new customers, retrieve a customer by ID, or retrieve all customers.
  • Purchase: Create new purchases.

Hands on

Create the project as a Spring Boot project with the dependencies provided inside the pom file at this link: POM

I have named it graphql-dgs-netflix.

GraphQL Schema Creation

We organize our GraphQL schemas in the /src/main/resources/schema directory. Netflix DGS will automatically load the schemas from this directory. Each scenario will have its own file with .graphql extension.

Below is the customer.graphqls file:

type QueryResolver {
customers: [Customer]
customer(id: ID!): Customer!
}

type MutationResolver {
createCustomer(customer: CustomerInput!): Customer
}

input CustomerInput {
firstName: String!
lastName: String!
email: String!
phone: String!
state: String!
city: String!
street: String!
amount: Float
}

type Customer {
id: ID!
firstName: String!
lastName: String!
email: String!
phone: String!
state: String!
city: String!
street: String!
amount: Float
}

schema {
query: QueryResolver
mutation: MutationResolver
}

In this schema, we define two new types: QueryResolver for read operations and MutationResolver for write operations. These types are bound to the respective query and mutation operations in GraphQL through the following schema definition.

schema {
query: QueryResolver
mutation: MutationResolver
}

Now, let’s look at shop.graphqls file

extend type QueryResolver {
shops: [Shop]
shop(id: ID!): Shop!
}

extend type MutationResolver {
createShop(shop: ShopInput!): Shop
}

input ShopInput {
name: String!
email: String!
phone: String!
state: String!
city: String!
street: String!
}

type Shop {
id: ID!
name: String!
email: String!
phone: String!
state: String!
city: String!
street: String!
items: [Item]
}

Notice that we extend QueryResolver and MutationResolver because we cannot define multiple instances of the same type, so we use extend.

Similarly in item.graphqls and purchase.graphqls. All GraphQL schema files can be found on GitHub at this link: GraphQL Schemas

Entities Creation

We are creating entities that map between our relational database and GraphQL schema.

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class Customer {
@Id
@GeneratedValue
private UUID id;
private String firstName;
private String lastName;
private String email;
private String phone;
private String state;
private String city;
private String street;
private double amount;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private Set<Purchase> items;
}

The Customer entity is the only example listed here. Other entities, including Item, Purchase, Shop can be found on GitHub at this link: Entities

Mutation Objects Creation

We have defined mutation scenarios for write operations, as discussed earlier. Now, we’ll create corresponding mutation objects. These objects will be used to handle writing requests, with their names matching those specified in the GraphQL schema.

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class CustomerInput {
private String firstName;
private String lastName;
private String email;
private String phone;
private String state;
private String city;
private String street;
private double amount;
}

The CustomerInput mutation object is the only example listed here. Other mutation objects, including ItemInput, PurchaseInput, ShopInput can be found on GitHub at this link: Mutation Objects

Repository creation

We’ll use JpaRepository for the repository layer, leveraging its comprehensive JPA capabilities to save and query our database objects.

@Repository
public interface CustomerRepository extends JpaRepository<Customer, UUID> {
}

Other repositories can be found on GitHub at this link: Repositories

Netflix DGS Aspects

First, let’s explore some key annotations provided by Netflix DGS, which are essential for interpreting and resolving requests:

  • @DgsComponent: Indicates a class containing data fetchers.
  • @DgsData: Marks a method as a data fetcher. The method must be within a @DgsComponent class.
  • @DgsQuery: A shorthand to define data fetchers on the Query
  • @DgsMutation: A shorthand to define data fetchers on the Mutation
  • DgsMutation: Defines input arguments as method parameters in a data fetcher.

Another aspect to consider when working with DGS is the ability to create a custom context using the DgsCustomContextBuilder class. This can be utilized for logging, maintaining states, or storing information accessed via the DataFetchingEnvironment class.

Querying with Netflix DGS

Now, we’ll create a query class for the Customer using the annotations mentioned earlier.

@DgsComponent
@RequiredArgsConstructor
public class CustomerQuery {
private final CustomerRepository customerRepository;
private final CustomContextBuilder contextBuilder;

@DgsData(parentType = "QueryResolver", field = "customers")
public Iterable<Customer> findAll(DgsDataFetchingEnvironment dfe) {
var customers = (List<Customer>) customerRepository.findAll();
contextBuilder.customContext(null, null, customers, null).build();
return customers;
}

@DgsData(parentType = "QueryResolver", field = "customer")
public Customer findById(@InputArgument("id") String id, DgsDataFetchingEnvironment dfe) {
CustomContext customContext = DgsContext.getCustomContext(dfe);
var customers = customContext.getCustomers();
if (CollectionUtils.isEmpty(customers)) {
return customerRepository.findById(UUID.fromString(id))
.orElseThrow(DgsEntityNotFoundException::new);
}
var customer = customers.stream().filter(it -> it.getId().equals(UUID.fromString(id)))
.findFirst();
return customer.orElseGet(() -> customerRepository.findById(UUID.fromString(id))
.orElseThrow(DgsEntityNotFoundException::new));
}
}

Other query classes, including ItemQuery, PurchaseQuery, ShopQuery can be found on GitHub at this link: Querying with Netflix DGS

Mutation with Netflix DGS

Now, we’ll create a query class for the Customer using the annotations mentioned earlier.

@DgsComponent
@RequiredArgsConstructor
public class CustomerMutation {
private final CustomerRepository customerRepository;

@DgsData(parentType = "MutationResolver", field = "createCustomer")
public Customer createCustomer(@InputArgument("customer") CustomerInput customer) {
return customerRepository.save(Customer.builder().firstName(customer.getFirstName())
.lastName(customer.getLastName()).email(customer.getEmail())
.phone(customer.getPhone()).state(customer.getState()).city(customer.getCity())
.street(customer.getStreet()).amount(customer.getAmount()).build());
}
}

Other mutation classes, including ItemMutation, PurchaseMutation, ShopMutation can be found on GitHub at this link: Mutation with Netflix DGS

Test the app with GraphiQL

To launch the application, run GraphDgsNetflixApplication.main() method, it should run successfully on port 8080.

Open a browser and navigate to http://localhost:8080/graphiql to access the interface for executing queries.

Now, Let’s do some play around.

Start by creating a Shop:

Fetching all shops:

Find a shop by ID:

There are still more scenarios to explore, but I’ll leave that as homework for you!

We have just explored the concept of Netflix DGS and conducted a brief demonstration to observe its behavior when integrated with Spring Boot and GraphQL.

Hope you can find something useful!

The completed source code can be found in this GitHub repository: https://github.com/buingoctruong/graphql-dgs-netflix

--

--