• Latest
GraphQL in Microservices With Spring and Angular

GraphQL in Microservices With Spring and Angular

December 24, 2022
xQc reacts to Samsung Galaxy Z Flip 3 | Marques Brownlee

xQc reacts to Samsung Galaxy Z Flip 3 | Marques Brownlee

March 20, 2023
Random: Resident Evil 4 Animation Is Both Cute And Unsettling In Equal Measure

Random: Resident Evil 4 Animation Is Both Cute And Unsettling In Equal Measure

March 20, 2023
Ice Universe: Samsung Galaxy Flip5 to have much larger external screen, Fold5 to drop the gap

Ice Universe: Samsung Galaxy Flip5 to have much larger external screen, Fold5 to drop the gap

March 20, 2023
10 Things You Didn't Know About Marques Brownlee from MKBHD

10 Things You Didn't Know About Marques Brownlee from MKBHD

March 20, 2023
LG G7 ThinQ Impressions!

LG G7 ThinQ Impressions!

March 20, 2023
Best Xbox zombie games

Best Xbox zombie games

March 20, 2023
New Allies delayed indefinitely on console

New Allies delayed indefinitely on console

March 20, 2023
Moto RAZR 2 Impressions: Nostalgia Reloaded?

Moto RAZR 2 Impressions: Nostalgia Reloaded?

March 20, 2023
The Entire Mystery joins Xbox Game Pass soon

The Entire Mystery joins Xbox Game Pass soon

March 20, 2023
Apple Watch Review!

Apple Watch Review!

March 20, 2023
The TRUTH About OnePlus Nord!

The TRUTH About OnePlus Nord!

March 20, 2023
Honor 70 Lite announced with Snapdragon 480+ and 50MP camera

Honor 70 Lite announced with Snapdragon 480+ and 50MP camera

March 20, 2023
Advertise with us
Monday, March 20, 2023
Bookmarks
  • Login
  • Register
GetUpdated
  • Game Updates
  • Mobile Gaming
  • Playstation News
  • Xbox News
  • Switch News
  • MMORPG
  • Game News
  • IGN
  • Retro Gaming
  • Tech News
  • Apple Updates
  • Jailbreak News
  • Mobile News
  • Software Development
  • Photography
  • Contact
No Result
View All Result
GetUpdated
No Result
View All Result
GetUpdated
No Result
View All Result
ADVERTISEMENT

GraphQL in Microservices With Spring and Angular

December 24, 2022
in Software Development
Reading Time:14 mins read
0 0
0
Share on FacebookShare on WhatsAppShare on Twitter


The AngularAndSpringWithMaps project has been converted from REST endpoints to a GraphQL interface. The project uses Spring GraphQL to provide the backend interface. The Angular frontend uses the Angular  HttpClient to post the requests to the backend.

GraphQL vs REST From an Architectural Perspective

REST Endpoints

REST calls retrieve objects with their children. For different root objects, separate REST calls are sent. These calls can accumulate with the number of different root objects that the frontend requests. With relationships between the objects, sequential calls become necessary.

REST Endpoints

Rings for the Polygon ID’s are fetched. Then the locations for the ring ID’s are fetched, which is a real REST design. It would also be possible to create two endpoints. 

  1. An endpoint that returns the ‘CompanySite.’
  2. An endpoint that returns the ‘CompanySite’ with all its children.

Then the client can request the data from the right endpoint and gets what it needs with one request.

With a growing number of requirements, the number of endpoints for clients grows. The effort to support the new endpoints will grow accordingly.

GraphQL Interface

GraphQL can support queries where the requested result object can be specified.

GraphQL Interface

The client that requests a ‘CompanySite’ can specify in the query the Polygons/Rings/Locations that the server needs to provide as children to the client. That creates a very flexible interface for the frontend but needs a backend that can support all possible queries in an efficient implementation.

Considerations for a Microservice Architecture

REST endpoints and GraphQL interfaces can have different use cases.

REST Endpoints

The different REST endpoints make it easier to implement additional logic for the results. For example, calculating sums and statistics for paying users of the requested objects. The more specialized endpoints can support these features easier.

Specialized Servers

GraphQL Interfaces

The GraphQL interface is more generic in its result structure. That makes it a good fit for an implementation that collects the requested data and returns it to the client. Smarter clients that implement more of the business logic are a good fit for this architecture. They can request the data required to provide the required features.

QraphQL Server

The GraphQL server can use databases or other servers to read its data. The data reads need to be optimized for all query shapes (support for partial data requests). Due to the generic interface, the GraphQL server will have less knowledge of its clients. 

GraphQL Implementation in a Microservice

The AngularAndSpringWithMaps project uses a GraphQL interface.

GraphQL in an Angular Frontend

GraphQL clients for Angular are available, but in this use case, Angular Services with the HttpClient are used. The GraphQLService implements the queries and mutations: 

export interface GraphqlOptions {
    operationName: string;
    query: string;
    variables?: { [key: string]: any};
}

@Injectable()
export class GraphqlService {

  constructor(private http: HttpClient) { }
  
  public query<T>(options: GraphqlOptions): Observable<T> {
    return this.http
      .post<{ data: T }>(`/graphql`, {
	    operationName: options.operationName,
        query: options.query,
        variables: options.variables,
      })
      .pipe(map((d) => d.data));
  }

  public mutate<T>(options: GraphqlOptions): Observable<any> {
    return this.http
      .post<{ data: T }>(`/graphql`, {
	    operationName: options.operationName,
        query: options.query,
        variables: options.variables,
      })
      .pipe(map((d) => d.data));
  }
}

The GraphQLOptions interface defines the parameters for the ‘query’ and ‘mutate’ methods of the GraphQLService. 

The GraphQLService is provided by the MapsModule and implements the ‘query’ and ‘mutate’ methods. Both methods post the content of the GraphQLOptions to the /graphql path. The posted object contains the properties ‘operationName,’ ‘query,’ and ‘variables.’ The returned result uses an RxJS pipe to unwrap the data in the result.

The CompanySiteService uses the GraphQLService to request the ‘CompanySites’ by ‘name’ and ‘year:’

@Injectable()
export class CompanySiteService {
   ...
   public findByTitleAndYear(title: string, year: number): 
      Observable<CompanySite[]> {
         const options = { operationName: 'getCompanySiteByTitle', 
            query: 'query getCompanySiteByTitle($title: String!, $year: 
            Long!) { getCompanySiteByTitle(title: $title, year: $year) { 
            id, title, atDate }}', variables: { 'title': title, 'year': 
            year } } as GraphqlOptions;
	 return this.mapResult<CompanySite[],CompanySite[]>
            (this.graphqlService.query<CompanySite[]>(options), 
            options.operationName);
   }

   public findByTitleAndYearWithChildren(title: string, year: number):  
      Observable<CompanySite[]> {
      const options = { operationName: 'getCompanySiteByTitle', query: 
         'query getCompanySiteByTitle($title: String!, $year: Long!) { 
         getCompanySiteByTitle(title: $title, year: $year) { id, title, 
         atDate, polygons { id, fillColor, borderColor, title, longitude, 
         latitude,rings{ id, primaryRing,locations { id, longitude, 
         latitude}}}}}', variables: { 'title': title, 'year': year } } as 
         GraphqlOptions;
      return this.mapResult<CompanySite[],CompanySite[]>
         (this.graphqlService.query<CompanySite[]>(options), 
         options.operationName);
  }
  ...

The findByTitleAndYear(...) method creates the GraphQLOptions with a query that requests ‘id, title, atDate’ of the ‘CompanySite.’

The findByTitleAndYearWithChildren(..) method creates the GraphQLOptions with a query that requests the ‘CompanySites’ with all its children and their properties and children. 

The ‘operationName’ is used in the mapResult(...) method to unwrap the result. 

Conclusion Frontend

In this use case, Angular provided the needed tools, and GraphQL is easy enough to use with Angular only. To develop the GraphQL queries for the backend, GraphQL (/graphiql) can help. 

GraphQL in the Spring Boot Backend

Spring Boot provides good support for GraphQL backend implementations. Due to the many options in the queries, the implementation has to be much more flexible than a REST implementation.

GraphQL Schema

The schema is in the schema.graphqls file:

scalar Date
scalar BigDecimal
scalar Long

input CompanySiteIn {
	id: ID!
	title: String!
	atDate: Date!
	polygons: [PolygonIn]
}

type CompanySiteOut {
	id: ID!
	title: String!
	atDate: Date!
	polygons: [PolygonOut]
}

...

type Query {
    getMainConfiguration: MainConfigurationOut
    getCompanySiteByTitle(title: String!, year: Long!): [CompanySiteOut]
    getCompanySiteById(id: ID!): CompanySiteOut
}

type Mutation {
   upsertCompanySite(companySite: CompanySiteIn!): CompanySiteOut
   resetDb: Boolean
   deletePolygon(companySiteId: ID!, polygonId: ID!): Boolean
}

The ‘scalar Date’ imports the ‘Date’ datatype from the graphql-java-extended-scalars library for use in the schema file. The same is implemented for the data types ‘BigDecimal’ and ‘Lo.’

The ‘input’ datatypes are for the upsertCompanySite(...) to call in the ‘Mutation’ type.

The ‘type’ datatypes are for the return types of the ‘Mutation’ or ‘Query’ calls. 

The ‘Query’ type contains the functions to read the data from the GraphQL interface. 

The ‘Mutation’ type contains the functions to change the data with the GraphQL interface. The ‘CompanySiteIn’ input type is a tree of data that is posted to the interface. The interface returns the updated data in ‘CompanySiteOut.’

GraphQL Configuration

The additional types of the graphql-java-extended-scalars library need to be configured in Spring GraphQL. That is done in the GraphQLConfig:

@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
   RuntimeWiringConfigurer result = wiringBuilder -> 
      wiringBuilder.scalar(ExtendedScalars.Date)		    
         .scalar(ExtendedScalars.DateTime)
         .scalar(ExtendedScalars.GraphQLBigDecimal)		  
         .scalar(ExtendedScalars.GraphQLBigInteger)
         .scalar(ExtendedScalars.GraphQLByte)
         .scalar(ExtendedScalars.GraphQLChar)
         .scalar(ExtendedScalars.GraphQLLong)		 
         .scalar(ExtendedScalars.GraphQLShort)
         .scalar(ExtendedScalars.Json)
         .scalar(ExtendedScalars.Locale)
	 .scalar(ExtendedScalars.LocalTime)
         .scalar(ExtendedScalars.NegativeFloat)
	 .scalar(ExtendedScalars.NegativeInt)
         .scalar(ExtendedScalars.NonNegativeFloat)
	 .scalar(ExtendedScalars.NonNegativeInt)
         .scalar(ExtendedScalars.NonPositiveFloat)
 	 .scalar(ExtendedScalars.NonPositiveInt)
         .scalar(ExtendedScalars.Object)
         .scalar(ExtendedScalars.Time)
	 .scalar(ExtendedScalars.Url)
         .scalar(ExtendedScalars.UUID);
      return result;
}

The additional datatypes of the graphql-java-extended-scalars library are registered with the ‘wiringBuilder.’

GraphQL Controllers

The ‘Query’ and ‘Mutation’ requests are implemented in the ConfigurationController and the CompanySiteController. The CompanySiteController is shown here:

@Controller
public class CompanySiteController {
   private static final Logger LOGGER =  
      LoggerFactory.getLogger(CompanySite.class);
   private final CompanySiteService companySiteService;
   private final EntityDtoMapper entityDtoMapper;
   private record Selections(boolean withPolygons, boolean withRings,
      boolean withLocations) {}

   public CompanySiteController(CompanySiteService companySiteService, 
      EntityDtoMapper entityDtoMapper) {
      this.companySiteService = companySiteService;
      this.entityDtoMapper = entityDtoMapper;
   }

   @QueryMapping
   public Mono<List<CompanySiteDto>> getCompanySiteByTitle(
      @Argument String title, @Argument Long year,
      DataFetchingEnvironment dataFetchingEnvironment) {
      Selections selections = createSelections(dataFetchingEnvironment);
      List<CompanySiteDto> companySiteDtos = 
         this.companySiteService.findCompanySiteByTitleAndYear(title, 
             year, selections.withPolygons(), selections.withRings(), 
             selections.withLocations()).stream().map(companySite -> 
                this.entityDtoMapper.mapToDto(companySite))
		.collect(Collectors.toList());
      return Mono.just(companySiteDtos);
   }

   private Selections createSelections(DataFetchingEnvironment 
      dataFetchingEnvironment) {
      boolean addPolygons = 
         dataFetchingEnvironment.getSelectionSet().contains("polygons");
      boolean addRings = 
         dataFetchingEnvironment.getSelectionSet().getFields().stream()
	    .anyMatch(sf -> "rings".equalsIgnoreCase(sf.getName()));
      boolean addLocations = 
         dataFetchingEnvironment.getSelectionSet().getFields().stream()
 	 .filter(sf -> "rings".equalsIgnoreCase(sf.getName()))
         .flatMap(sf -> Stream.of(sf.getSelectionSet()))
	 .anyMatch(sf -> sf.contains("locations"));
      Selections selections = new Selections(addPolygons, addRings, 
         addLocations);
      return selections;
}

The CompanySiteController implements the ‘CompanySite’ related methods of the GraphQL schema. It gets the CompanySiteService and the EntityDtoMapper injected.

The getCompanySiteByTitle(…) method gets the arguments with the annotations that are defined in the GraphQL schema and the DataFetchingEnvironment. The DataFetchingEnvironment is used to call the createSelections(…) method. The createSelections(…) method uses the SelectionSets to find the requested children, like ‘polygons,’ ‘rings,’ and ‘locations’ and creates a record with booleans to return the requested child types. The method findCompanySiteByTitleAndYear(…) of the CompanySiteService is then used to read the ‘CompanySites.’ The result entities are then mapped with the EntityDtoMapper into DTO’s and are returned. The EntityDtoMapper is able to handle not initialized JPA properties. Spring GraphQL filters and returns only the requested properties of the DTO’s.

GraphQL Services

The CompanySiteService implements the flexible selection structure of the data:

@Transactional
@Service
public class CompanySiteService {
   private final CompanySiteRepository companySiteRepository;
   private final PolygonRepository polygonRepository;
   private final RingRepository ringRepository;
   private final LocationRepository locationRepository;
   private final DataFetcher<Iterable<CompanySite>> dataFetcherCs;
   private final EntityManager entityManager;

   public CompanySiteService(
      CompanySiteRepository companySiteRepository, 
      PolygonRepository polygonRepository, RingRepository ringRepository, 
      LocationRepository locationRepository, 
      EntityManager entityManager) {
      
      this.companySiteRepository = companySiteRepository;
      this.polygonRepository = polygonRepository;
      this.ringRepository = ringRepository;
      this.locationRepository = locationRepository;
      this.entityManager = entityManager;
   }

   public Collection<CompanySite> findCompanySiteByTitleAndYear(String 
      title, Long year, boolean withPolygons,
      boolean withRings, boolean withLocations) {
      
      if (title == null || title.length() < 2 || year == null) {
         return List.of();
      }
      LocalDate beginOfYear = LocalDate.of(year.intValue(), 1, 1);
      LocalDate endOfYear = LocalDate.of(year.intValue(), 12, 31);
      title = title.trim().toLowerCase();
      List<CompanySite> companySites = this.companySiteRepository
         .findByTitleFromTo(title, beginOfYear, endOfYear).stream()
         .peek(myCompanySite ->   
             this.entityManager.detach(myCompanySite)).toList();
      companySites = addEntities(withPolygons, withRings, withLocations,
         companySites);
      return companySites;
   }

   private List<CompanySite> addEntities(boolean withPolygons, boolean 
      withRings, boolean withLocations,	List<CompanySite> companySites) {
      
       if (withPolygons) {
          Map<Long, List<Polygon>> fetchPolygons = 
             this.fetchPolygons(companySites);
	  Map<Long, List<Ring>> fetchRings = !withRings ? Map.of() : 
             this.fetchRings(fetchPolygons.values()
                .stream().flatMap(List::stream).toList());
	  Map<Long, List<Location>> fetchLocations = !withLocations ? 
             Map.of() : this.fetchLocations(fetchRings.values()
                .stream().flatMap(List::stream).toList());
	  companySites.forEach(myCompanySite -> {
             myCompanySite.setPolygons(
               new HashSet<>(fetchPolygons.
                  getOrDefault(myCompanySite.getId(), List.of())));
	     if (withRings) { 
		myCompanySite.getPolygons()
                   .forEach(myPolygon -> {
		      myPolygon.setRings(
                         new HashSet<>(fetchRings.getOrDefault(
                            myPolygon.getId(), List.of())));
			if (withLocations) {
			   myPolygon.getRings().forEach(myRing -> {
			      myRing.setLocations(
			         new HashSet<>(fetchLocations
                                    .getOrDefault(myRing.getId(), 
                                        List.of())));
		          });
	               }
                });
	     }
         });
      }
      return companySites;
   }

   public Map<Long, List<Polygon>> fetchPolygons(List<CompanySite> 
      companySites) {
      
      List<Polygon> polygons = this.polygonRepository
	.findAllByCompanySiteIds(companySites.stream().map(cs -> 
           cs.getId()).collect(Collectors.toList()))
	   .stream().peek(myPolygon -> 
              this.entityManager.detach(myPolygon)).toList();
      return companySites.stream().map(CompanySite::getId)
         .map(myCsId -> Map.entry(findEntity(companySites, 
             myCsId).getId(), findPolygons(polygons, myCsId)))
	 .collect(Collectors.toMap(Map.Entry::getKey, 
            Map.Entry::getValue));
   }
...
}

The CompanySiteService gets the needed repositories and the EntityManager injected.

The method findCompanySiteByTitleAndYear(…) checks its parameters and then prepares the parameters of the findByTitleFromTo(…) method for the CompanySiteRepository. The method returns the matching ‘CompanySite’ entities and uses a Stream to detach them. The method addEntities(...) is then called to retrieve the child entities of the requested ‘CompanySite’ entities. For each child layer (such as ‘Polygons,’ ‘Rings,’ ‘Locations’) all child entities are collected. After all the parent entities are collected the children are selected in one query to avoid the ‘+1’ query problem. The child entities are also detached to support partial loading of the child entities and to support the DTO mapping. 

The fetchPolygons(…) method uses the findAllByCompanySiteIds(…) method of the PolygonRepository to select all the matching Polygons of all the ‘CompanySite ID’s.’ The Polygons are returned in a map with the ‘CompanySite ID’ as the key and the Polygon entity list as the value. 

GraphQL Repositories

To support the loading of the child entities by layer. The child entities have to be selected by parent ‘ID.’ That is, for example, done in the JPAPolygonRepository:

public interface JpaPolygonRepository extends JpaRepository<Polygon, 
   Long>, QuerydslPredicateExecutor<Polygon> {

   @Query("select p from Polygon p inner join p.companySite cs 
              where cs.id in :ids")
   List<Polygon> findAllByCompanySiteIds(@Param("ids") List<Long> ids);
}

The findAllByCompanySiteIds(…) method creates a JQL query that joins the ‘CompanySite’ entity and selects all Polygons for the ‘CompanySite IDs.’ That means one query for all Polygons. Four layers (such as ‘CompanySite,’ ‘Polygon,’ ‘Ring,’ and ‘Location’) equal four queries.

Conclusion Backend

To avoid the ‘+1’ problem with entity loading and to load only the needed entities, this layered approach was needed. A REST endpoint that knows how many child layers have to be loaded can be implemented more efficiently (one query). The price to pay for that efficiency are more endpoints.

Conclusion

REST and GraphQL both have advantages and disadvantages.

  • Because the returned results are less flexible, REST can support more logic at the endpoints more easily.
  • GraphQL supports the specification of the requested results. That can be used to request more flexible results that can be smaller but needs a flexible backend to efficiently support all possible result shapes.

Each project needs to decide what architecture better fits the given requirements. For that decision, the requirements and the estimated development effort for the frontend and backend implementations need to be considered.



Source link

ShareSendTweet
Previous Post

‘Melatonin’, ‘Hyper Gunsport’, Plus Today’s Other New Releases and the Latest Sales – TouchArcade

Next Post

Twitter Blue subscribers now get prioritized rankings in conversations

Related Posts

AWS CodeCommit and GitKraken Basics

March 20, 2023
0
0
AWS CodeCommit and GitKraken Basics
Software Development

Git is a source code management system that keeps track of the changes made to their codebase and collaborates with other...

Read more

Reliability Is Slowing You Down

March 19, 2023
0
0
Reliability Is Slowing You Down
Software Development

Three Hard Facts First, the complexity of your software systems is through the roof, and you have more external dependencies...

Read more
Next Post
Twitter Blue subscribers now get prioritized rankings in conversations

Twitter Blue subscribers now get prioritized rankings in conversations

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

© 2021 GetUpdated – MW.

  • About
  • Advertise
  • Privacy & Policy
  • Terms & Conditions
  • Contact

No Result
View All Result
  • Game Updates
  • Mobile Gaming
  • Playstation News
  • Xbox News
  • Switch News
  • MMORPG
  • Game News
  • IGN
  • Retro Gaming
  • Tech News
  • Apple Updates
  • Jailbreak News
  • Mobile News
  • Software Development
  • Photography
  • Contact

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms bellow to register

All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?