Banuprakash C
Full Stack Architect,
Co-founder Lucida Technologies Pvt Ltd.,
Corporate Trainer,
Email: banuprakashc@yahoo.co.in
https://www.linkedin.com/in/banu-prakash-50416019/
https://github.com/BanuPrakash/GraphQL
===================================
Softwares Required:
-
openJDK 17 https://jdk.java.net/java-se-ri/17
-
Eclipse for JEE
https://www.eclipse.org/downloads/packages/release/2022-09/r/eclipse-ide-enterprise-java-and-web-developers
Lombok java -jar lombok.1.18.26.jar
===================================
Spring Framework provides a light weight container for building enterprise applications
- Life Cycle management of beans
- wiring dependencies
- Enterprise application integration
Any object managed by spring container -=> bean Wiring dependecies: Dependency injection
Eclipse ==> Help ==> Eclipse Market Place ==> Search [STS] ==> GO ==> Install Spring Tool suite 4
Spring Boot is a framework on top of Spring framework
- Highly opiniated framework configures lots of api out-of-the-box depending on Context
- If we choose to connect to RDBMS --> Gives HikariCP database connection pool [ C3p0, DMDS, ..]
- If we choose Web application dev --> Gives Embedded Tomcat Server [ jetty, reactor, ..]
- If we build RESTful ws --> Gives Jackson apis to convert Java <--> JSON [ GSON, Jettison, Moxy, ...]
- JPA --> gives Hibernate as JPAVendor [ Toplink, KODO, JDO, EclipseLink, OpenJPA, ...]
====
Spring creates instances of class which has one of these annotations:
- @Component
- @Repository ==> additional capabilities like handle SQL related exception
- @Service
- @Configuration
- @Controller
- @RestController
@Repository public class ProductDaoDBImpl implements ProductDao {
Name of the Object ==> productDaoDBImpl ==> Singleton by default
@SpringBootApplication
- @ComponentScan ==> scan current package and sub-packages for above mentioned 6 annotations and create instance.
- @Configuration
- @EnableAutoConfiguration ==> built-in configuration objects [ many ...]
@Autowired ==> look in the container is there an instance of given interface/class ==> Wiring Internally Spring boot uses : ByteBuddy / JavaAssist/ CGLib
=====
@Repository public class ProductDaoDBImpl implements ProductDao {
@Repository public class ProductDaoJpaImpl implements ProductDao {
@Service public class AppService {
@Autowired
private ProductDao productDao; // issue
Solution 1:
Mark one of them as @Primary
@Primary @Repository public class ProductDaoJpaImpl implements ProductDao {
Solution 2: Remove @Primary
and add @Qualifier
@Service public class AppService { @Autowired @Qualifier("productDaoJpaImpl") private ProductDao productDao;
Solution 3: using @Profile Remove @Primary and @Qualifier
@Profile("prod") @Repository public class ProductDaoJpaImpl implements ProductDao {
@Profile("dev") @Repository public class ProductDaoDBImpl implements ProductDao {
@Service public class AppService { @Autowired private ProductDao productDao;
src/main/resources application.properties spring.profiles.active=prod
Command Line argument java -jar name.jar --spring.profiles.active=prod
======
JPA and @Bean
Factory Method --> @Bean
objects of 3rd party classes
@Configuration
public class AppConfig {
@Bean
public DataSource getDataSource() {
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "org.postgresql.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:postgresql://localhost/testdb" );
cpds.setUser("swaldman");
cpds.setPassword("test-password");
// the settings below are optional -- c3p0 can work with defaults
cpds.setMinPoolSize(5);
cpds.setAcquireIncrement(5);
cpds.setMaxPoolSize(20);
return cpds;
}
}
@Repository
public class ProductRepo {
@Autowired
DataSource ds;
...
}
===========
ORM and JPA Specification
Object Relational Mapping --> Framework for CRUD operations on RDBMS ORM frameworks:
- Hiberante
- Toplink
- KODO
- JDO
- OpenJPA
products table id name amount quantity 1 A 5344.22 100
@Entity
@Table("products)
public class Product {
@Id
int id;
String name;
@Column(name="amount")
double price;
@Column(name="quantity")
int qty;
}
ORM
- DataSource --> Pool of database connection
- PersistenceContext --> env where entities are managed [ @Entity ]
- EntityManager is a interface to manage entites [ Sync with database]
- EntityManagerFactory
@Configuration
public class AppConfig {
@Bean
public DataSource getDataSource() {
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "org.postgresql.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:postgresql://localhost/testdb" );
cpds.setUser("swaldman");
cpds.setPassword("test-password");
// the settings below are optional -- c3p0 can work with defaults
cpds.setMinPoolSize(5);
cpds.setAcquireIncrement(5);
cpds.setMaxPoolSize(20);
return cpds;
}
@Bean
public EntityManagerFactory emf(DataSource ds) {
LocalContainerEntityMAnagerFactoryBean emf = ..
emf.setDataSource(ds);
emf.setJpaVendor(new HibernateJpaVendor());
emf.setPackagesToScan("com.adobe.prj.entity");
// other properties
}
}
@Repository
public class ProductRepo {
@PersistenceContext
EntityManager em;
public void saveProduct(Product p) {
em.save(p); // SQL --> MySQL, H2, Oracle
/*
try {
Connection con = DriverManager.getConnection(;..);
PreparedStatement ps = con.preparedStatement("insert into products values(?,?, ? , ?));
ps.setInt(1, p.getId());
...
ps.executeUpdate();
} catch(SQLException ex) {
..
} finally {
con.close();
}
*/
}
public List<Product> getPRoducts() {
return em.findAll();
}
}
=====
Spring Data JPA provides abstraction over JPA org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 runtime
public interface ProductRepo extends JpaRepository<Product, Integer> {
}
No need for @Repository class --> Spring Data Jpa generates class with methods in JpaRepository
===============================
Spring MVC org.springframework.boot spring-boot-starter-web
@Controller @RequestMapping("api/products") public class ProductController {
@GetMapping()
m1() {
}
@PostMapping() {
}
}
GraphQL
GraphQL --> Query Language for your API, not tied to any specific database or storage engine GraphQL --> server side runtime for executing queries
json / xml / csv => different representations of state of data
RESTful Web services:
- plural-nouns for resources
- HTTP methods for actions [ GET , POST, PUT, PATCH, DELETE ]
GET http://localhost:8080/api/products gets all products
GET http://localhost:8080/api/products?category=mobile gets sub-set of products ==> filter by category
GET http://localhost:8080/api/products/3 gets product by id ==> 3
POST http://localhost:8080/api/products
Payload contains new product data which has to added to "products" resources
GraphQL over REST downsides:
- Multiple requests for multiple resources
- Waterfall network requests http://adobe.com/clients ==> Client data ==> all clients [ 5 clients] http://adobe.com/clients//projects ==> http://adobe.com/clients/1/projects [pids] http://adobe.com/clients//projects//team
- Over-fetching of data
- No under-fetching of data
- Schema ==> Contract between client and server ==> Schema first approach
Schemas and Types:
Type System: GraphQL query language --> selecting fields and objects
POST http://localhost:8080/graphql
{ product { name price supplier { name phone } } }
Schema Definition Language - SDL
Define object type Product { id:Int name:String price:Float }
type Book { title:String amount:Float }
Scalar Types: Int, Float, String, Boolean, ID
extended-scalar
Query Type ==> Special Object type that defines all the top level entry points for queries that clients can executes
productmodule.graphqls type Query { products:[Product] books:[Book] }
Client: { products { name price } }
ordermodule.graphqls
extend type Query { orders:[Order] }
========== Operation Type schema { Query Mutation Subscription }
org.springframework.boot spring-boot-starter-graphql===========
Day 2
Day 1: Spring Boot Framework on top of Spring, Annotations, JPA for ORM, H2 as in memory database
src/main/resources application.properties ==> DB configuration, server.port=9999, any other configurations,...
@SpringBootApplication ==> class with this annotation contains main() method --> entry point to start Spring Container [ApplicationContext]
GraphQL with Spring Boot 3.0.6 ==> JDK 17+
TypeSystem and Schema Definition Language Scalar Types : Int, Float, Boolean, String, ID { serialized string}
type Query { products:[Product] }
// Object type type Product { id:Int! name:String! price:Float }
Root Object type : Opertation Type
schema { query, mutation, subscription }
Http as Server Transports org.springframework.boot spring-boot-starter-web
Tomcat as web server by default running on port 8080
POST http://localhost:8080/graphql
configure application.properties
spring.graphql.path=/graphql
spring.graphql.graphiql.enabled=true
graphiql is GraphQL client --> just like POSTMAN for RESTful WS
spring.graphql.graphiql.path=/graphiql
spring.graphql.schema.locations=classpath:graphql/**/ spring.graphql.schema.file-extensions=.graphqls,.gqls
http://localhost:8080/graphiql?path=/graphql
"query" is default operation
query {
products {
id
name
}
}
---
{
productById(id:2) {
id
name
}
}
Check:
- pom.xml ==> added web dependencies ==> for HTTP Server transport
- moved product.graphqls to "graphql" folder
- added DataFetcher for productById
Browser: http://localhost:8080/graphiql
==========================================
Spring / Spring Boot simplied writing DataFetcher
@Component
public class ProductQueryResolver implements GraphQlQueryResolver {
public List<Product> products() {
///
}
public Product productById(int id) {
//
}
}
Spring boot 2.7+ [ currently in Spring version 3.0] @SchemaMapping @QueryMapping @MutationMapping @SubscriptionMapping
on the lines of RESTful WS: @RequestMapping @GetMapping @PostMapping @PutMapping @PathMapping @DeleteMapping
@Controller
public class ProductQueryResolver {
@QueryMapping
public List<Product> products() {
///
}
}
===========
Eclipse --> Maven --> exisiting maven project --> VeternirayClinic
JPA Bidirectional Relationship
@Table("customers")
@Entity
public class Customer {
@Id
email;
name;
@OneToMany(mappedBy="customer")
List<Order> orders;
}
customers
email name
a@adobe.com Asha
b@adobe.com Beena
@Table("orders")
@Entity
public class Order {
@Id
@Column("order_id)
orderId;
total;
@ManyToOne
@JoinColumn("customer_fk")
Customer customer;
}
orders
order_id total customer_fk
120 899344 a@adobe.com
123 2334 b@adobe.com
124 9888 a@adobe.com
spring.profiles.active=mysql
application-hsqldb.properties application-mysql.properties
{
pets {
name
type {
id
name
}
}
}
JP-QL [ Class and fields] @Query("from Visit where pet.id = :pid")
same as SQL [ table and columns]
select * from visits where pet.id = 3
=====
PetType and Pet ==> Visit
Tasks: VisitController [visit -> get pet, vet and owner ] and OwnerController [ pets, visits]
Java Client for GraphiQL -> HttpGraphQlClient
type Query { pets:[Pet] pet(id:Int): Pet petTypes:[PetType] #visits:[Visit] #owners:[Owner] }
====
By Default in JPA: @ManyToOne is EAGER fetching and @OneToMany is Lazy Fetching
====
Unit Testing: Testing in isolation JUnit is default Test Framework, Mockito is default Mocking library WebGraphQlTester
MockMvc --> to perform CRUD operations programatically
=====
RuntimeWiring DataFetchers @Controller @QueryMapping ==> root query @SchemaMapping ==> filed mapping @Argument
type, query root operation
Day 3
Variables --> dynamic values for query, pass them separetly
POSTMAN: POST http://localhost:8080/graphql GraphQL
Query:
query PetWithOwner($pid:Int!){
pet(id:$pid) {
name,
type {
name
}
owner {
firstName
lastName
}
}
}
variable:
{
"pid":1
}
Same in graphiql
===========
Fragments: lets you construct set of resusable fields, which can be useed in queries
query PetWithOwner($pid:Int!){
pet(id:$pid) {
name,
type {
name
}
...OwnerWithPets
}
}
@ Fragment on Pet type of Object
fragment OwnerWithPets on Pet {
owner {
firstName
lastName
pets {
name
type {
name
}
}
}
}
Directives --> decorates part of GraphWL schema with additional configuration
Default directives @deprecated(reason: String) Marks the schema definition of a field or enum value as deprecated with an optional reason.
@skip(if: Boolean!) If true, the decorated field or fragment in an operation is not resolved by the GraphQL server. @include(if: Boolean!) If false, the decorated field or fragment in an operation is not resolved by the GraphQL server.
Example:
query PetWithOwner($pid:Int!, $withPets: Boolean!){
pet(id:$pid) {
name,
type {
name
}
...OwnerWithPets
}
}
fragment OwnerWithPets on Pet {
owner {
firstName
lastName
pets @include(if:$withPets){
name
type {
name
}
}
}
}
Variables:
{
"pid":1,
"withPets": false
}
Custom Scalars
https://github.com/graphql-java/graphql-java-extended-scalars
The Coercing interface is used by {@link graphql.schema.GraphQLScalarType}s to parse and serialise object values.
=======
Unions and interfaces
Union:
type Book {
title:String!
}
type Author {
name:String!
}
union SearchResult = Book | Author
type Query {
search(contains:String) : [SearchResult!]
}
interface Book {
title:String!
author:Author!
}
type TextBook implments Book {
title:String!
author:Author!
courses: [Course!]!
}
type OtherBook implements Book {
title:String!
author:Author!
someContent: String
}
---
interface Person {
id: Int!
firstName: String!
lastName: String!
}
type Owner implements Person {
id: Int!
firstName: String!
lastName: String!
address: String!
city: String!
telephone: String!
# A list of Pets this Owner owns
pets: [Pet!]!
}
# A Vetenerian
type Vet implements Person {
id: Int!
# The Vetenarian's first name
firstName: String!
# The Vetenarian's last name
lastName: String!
# What is this Vet specialized in? ==> EAGER fetching, no need for @SchemaMapping
specialties: [Specialty!]!
# All of this Vet's visits
visits: [Visit!]!
}
Query:
{
people {
firstName
lastName
__typename
...on Vet {
specialties {
name
}
}
...on Owner {
city
telephone
}
}
}
Pagination
- Offset based pagination pets(size:10, offset: 2)
size --> records offset --> from which page Controller Pageable pageable = PageRequest.of(page, size)
JPA Page findAll(Pageable pageable);
-
Cursor-based pagination pets(first:10, after:cursor); pets(first:10, before:cursor);
Advantage: REvserse pagination Disadvantage: No Random Access
Connection Type contains "edges" and "pageInfo" EdgeType --> cursor, node node --> scalar, object
{
petPage(first:2) {
edges {
cursor
node {
name
owner {
firstName
}
}
}
pageInfo {
hasPreviousPage
hasNextPage
startCursor
endCursor
}
}
}
{
petPage(first:2, after : "NA==") {
edges {
cursor
node {
name
owner {
firstName
}
}
}
pageInfo {
hasPreviousPage
hasNextPage
startCursor
endCursor
}
}
}
petPage(first:Int!, after:String) :PetConnection on Pet
===
Mutation:
mutation {
addVisit(input: {
petId: 1
vetId: 3
description: "Vaccination"
date: "2023/03/22"
}) {
description
}
}
Pending:
- DataLoader --> n + 1 problem
- Exception --> GraphQLError
- Subscription
- Filter using specification API
- JWT Security
=================
Day 3 Recap:
- Custom Scalar type --> graphql-extended-scalars --> dependencies --> Double, Long, DataTime, ...
*.graphqls
Scalar DateTime
--> Corecing interface --> serialize, parseValue and parseLiteral register with RuntimeWiringConfigurer just like DataFetchers
- Union and interfaces , fragments __typename, ...on
- variables
- Directives to be used with FIELD_DEFNITION, OBJECT, ArGUMENT register with RuntimeWiringConfigurer
- Pagination 5.1) based on RELAY specification Connection --> EDGE and PageINFO EDGE --> NODE and CURSOR
get first n records from cursor [ after or before ]
5.2) Offset
Day 4)
JPA Specification with GraphQL
JP-QL and SQL for dynamic queries will be difficult to write for requirements like E-commerce Search
Criteria API --> Object way to query the data
JPASpecification JpaSpecificationExecutor and Specification
OwnerRepository, OwnerSpecication and "filter package"
owners(page: Int, size: Int, filter: OwnerSpecification): OwnerSearchResult!
@QueryMapping
public OwnerSearchResult owners(@Argument Optional<Integer> page, @Argument Optional<Integer> size,
@Argument("filter") Optional<OwnerSpecification> filter) {
int pageNo = page.orElse(0);
int sizeNo = Math.min(size.orElse(20), 25);
final PageRequest pageRequest = PageRequest.of(pageNo, sizeNo);
return new OwnerSearchResult(ownerRepository.findAll(filter.orElse(null), pageRequest));
}
Client:
{
owners(page:0, size:2,filter:{
city: "Madison"
}) {
owners {
firstName
lastName
city
address
}
}
}
GraphQL DataLoaders
- Delay loading of field resolvers
- n + 1 problem
- Caching
n + 1 problem
pets { name owner { firstName } }
select * from pets ==> [ 1, 2, 3, 4, 5] --> 1 Query select * from owner where pet_id = 1 select * from owner where pet_id = 2 select * from owner where pet_id = 3 select * from owner where pet_id = 4 select * from owner where pet_id = 5
With DataLoader == we collect only owner ids of each pet ["o1", "o2","o3","o4", "o2"]
select * from owners where id in ["o1", "o2","o3","o4"]
org.springframework.boot spring-boot-starter-webfluxPublisher Flux --> emits o...n elements Mono --> emits o.. 1 element
visits:[Visit]
type Visit {
id:Int!
description:String
pet:Pet
date:Date!
treatingVet:Vet
}
pet field resolver
treatingVet field resolver
@QueryMapping
public List<Visit> visits(@Argument Optional<Integer> petId) {
....
}
@SchemaMapping(typeName = "Visit")
public CompletableFuture<Vet> treatingVet(Visit visit, DataLoader<Integer, Vet> dataLoader) {
if (visit.getVet() == null) {
return null;
}
log.info("Delegating loading of Vet with id {} from REST", visit.getVet().getId());
return dataLoader.load(visit.getVet().getId());
}
Check console:
{
visits {
description
date
treatingVet {
firstName
lastName
}
}
}
Spring Boot 3.0 introduced @BatchMapping for DataLoader implementation
{
books {
name
author
ratings {
rating
comment
}
}
}
Root Operations in GraphQL --> Query, Mutation, Subscription
webflux, websocket org.springframework.boot spring-boot-starter-websocket
WebFlux --> Reactive stream --> Flux , Mono, Observable --> Publishers
Admin Client needs to get a notification whenever a new visit to clinic happens Client subscribes for a Publisher
type Subscription { onNewVisit: Visit }
VisitController @SubscriptionMapping public Flux onNewVisit() { return visitPublisher.getPublisher(); }
Client subscribes:
subscription {
onNewVisit{
description
id
}
}
Client gets notification when New visit happens: mutation { addVisit(input: { petId: 1 vetId: 3 description: "Vaccination 434" date: "2023/03/22" }) { description } }
===========
Security --> Graphql --> Prefer Token based authrization for GraphQL --> JWT
Spring Security org.springframework.boot spring-boot-starter-security
====
@Autowired
DataSource ds;
@Autowired
public void configureJdbcAuth(AuthenticationManagerBuilder auth) {
auth.jdbcAuthentication().dataSource(dataSource); // appliation.properties --> dirver, url, username,
}
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
GraphQL, RESTful should be stateless --> No SessionTracking
Server authentication and sends Token to client Token contains claims --> subject {who you are?}, "issuer", "iat", "exp", authorites/roles
every request from client send token Tokens can be Opaque Tokens / JWT
org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-oauth2-authorization-server 1.0.1JWT
-
Symmetric key --> same key is used for both encryption and decryption "topsecret12357-dotask"
-
Asymmetric key --> Different keys for encryption (private key) and decryption (public key)
use openssl to generate private key and public key