GraphQL Server with Netflix DGS and Spring Boot
Definition:
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
What is Netflix DGS?
Netflix DGS (Domain Graph Service) is a GraphQL server framework written in Kotlin based on Spring Boot and is designed to have minimal external dependencies aside from Spring framework.
DGS also comes with code-gen plugin to generate Java or Kotlin code from GraphQL Schema.
GraphQL beginners — Start here
GraphQL API development
There are two of approaches to GraphQL API development as below
- Schema First API development: Here we define API with GraphQL schema file and provide service implementation for the Query/Mutation operations.
- Code First API development: Its opposite to above, here GraohQL schema gets generated at run time from the code definitions.
Netflix DGS prefers Schema-first development over code-first approach.
GraphQL Schema Types (Its from GraphQL — not specific to DGS)
- Query Type: To define read only operations (Eg: GET)
- Mutation Type: To define data alter operations (Eg: PUT, POST, DELETE, PATCH)
- Input Type: To define user-defined json type used as an request parameter with Query/Mutation operations.
- Output Type: To define user-defined json type used as an response type with Query/Mutation operations.
- Enum Type: To define constants — can be used both for properties within input/output types.
- Scalar Type: All GraphQL default primitives types are considered as scalars.
- Interface Type: We can use interface type as response parameter.
Let’s start with a simple example:
DGS framework dependencies: (hyperlink-attached)
- Maven:
<dependency>
<groupId>com.netflix.graphql.dgs</groupId>
<artifactId>graphql-dgs-spring-boot-starter</artifactId>
<!-- Set the latest framework version! -->
<version>3.11.0</version>
</dependency>
- Gradle:
repositories {
mavenCentral()
}dependencies {
implementation "com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:latest.release"
}
- Gradle + Kotlin:
repositories {
mavenCentral()
}dependencies {
implementation("com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:latest.release")
}
Let’s start with GraphQL schema file
Here we are using couple of model objects — Account and Customer to understand some GraphQL concepts and its usage when we have multiple entities with hibernate mappings.
Here we have Query, Mutation, Input, Enum and Scalar types
type Query {
customers: [Customer]
}
type Mutation {
customer(customerInput: CustomerInput): Customer
}
type Customer {
customerNumber: String!
name: String!
gender: String!
contact: Int!
mail: String
accounts: [Account]
}
type Account {
accountId: String
accountNumber: Int
accountStatus: AccountStatus
accountBalance: Float
}
input CustomerInput {
name: String!
gender: String!
contact: Int!
mail: String
accounts: [AccountInput]
}
input AccountInput {
accountNumber: Int
accountStatus: AccountStatus
accountBalance: Float
}
enum AccountStatus {
Active, Inactive, Closed
}
Implement Data Fetcher with @DgsComponent
@DgsComponent — It acts like @RestController annotation.
@DgsComponent
public class CustomerDataFetcher {
@Autowired
CustomerRepository customerRepository;
@Autowired
AccountRepository accountRepository; @DgsData methods @DgsMutation methods @DgsQuery methods
}
GraphQL Query with @DgsQuery and @DgsData annotation
@DgsQuery — same as ‘@GetMapping’ annotation
Its a method level annotation used to make the method as a datafetcher. It has only one parameter — “field”. It will be used to perform fetch operation.
Here the parentType is “Query” — derived from the annotation itself and “field” — if specified picks from the value provided or else it will be derived from the method name.
@DgsQuery(field = "customers")
public List<Customer> customers() {
return customerRepository.findAll();
}
@DgsData — same as ‘@RequestMapping’ annotation
Its a method level annotation used to make the method as a datafetcher. It has two parameters — “parentType” and “field”.
- parentType — its a type that contains field.
- field — indicates the field the datafetcher is responsible for.
Point to note here, parameter ‘field’ is an optional parameter which will be derived from method name.
We can’t define @DgsQuery and @DgsMutation in a single method, instead we can use @DgsData with parentType as ‘Query’ or ‘Mutation’.
@DgsData(parentType = "Query", field = "customers")
public List<Customer> customers() {
return customerRepository.findAll();
}@DgsData(parentType = "Customer", field = "accounts")
public List<Account> accounts(DgsDataFetchingEnvironment dgsDataFetchingEnvironment) {
Customer customer = dgsDataFetchingEnvironment.getSource();
List<Account> accountList = new ArrayList<>();
for (Account account : customer.getAccounts()) {
Account accountResponse = accountRepository.findById(account.getAccountId()).get();
accountList.add(accountResponse);
}
return accountList;
}
GraphQL Mutation with @DgsMutation annotation
@DgsMutation — same as ‘@PostMapping, @PutMapping, @DeleteMapping’ annotation
Its a method level annotation used to make the method as a datafetcher. It has only one parameter — “field”. It will be used to perform persist/update/delete operations.
Here the parentType is “Mutation” — derived from the annotation itself and “field” — if specified picks from the value provided or else it will be derived from the method name.
@DgsMutation
public Customer customer(CustomerInput customerInput) {
Customer customer = Customer.builder()
.contact(customerInput.getContact())
.name(customerInput.getName())
.gender(customerInput.getGender())
.mail(customerInput.getMail())
.accounts(mapCustomerAccounts(customerInput.getAccounts()))
.build();
Customer customerResponse = customerRepository.save(customer);
return customerResponse;
}
Let’s start testing application from GraphiQL web console:
Model class — Account
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "account")
public class Account { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String accountId;
private Long accountNumber;
private String accountStatus;
private Double accountBalance;
}
Model class — Customer
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "Customer")
public class Customer { @Id
@Column(name = "CUSTOMER_NUMBER")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String customerNumber;
private String name;
private String gender;
private Long contact;
private String mail; @OneToMany(cascade = CascadeType.ALL)
private List<Account> accounts;
}
JPA Repository — Account and Customer
@Repository
public interface CustomerRepository extends JpaRepository<Customer, String> {}@Repository
public interface AccountRepository extends JpaRepository<Account, String> {}
Let’s start testing application from GraphiQL web console:
By Selecting “CustomerMutation” we are performing POST operation which makes an entry into our H2 database.
By Selecting “CustomerQuery” we are performing GET operation, we are fetching all the customers from H2 database.
I hope you’ve found this article helpful in understanding basics of Netflix DGS framework. Here we started with the basics of Netflix DGS and how it can be used to write GraphQL components and how it can be used to implement Query and Mutation with an example to persist and read data from H2 DB.
The complete code for the above example can be found on GitHub
References: