ZEMOSO ENGINEERING STUDIO
April 30, 2019
9 min read

GraphQL with Java Spring Boot and Apollo Angular for Agile platforms

GraphQL can be used with any backend framework or programming language. Lets first understand certain GraphQL concepts, before we learn how to implement it. 

Background — GraphQL concepts

Schema: GraphQL schema is a description of the data that clients can request from a GraphQL Application Programming Interface (API). A client could use this schema, and construct a request, to fetch/update the relevant data on the server. 

Queries and mutations: Two different types of GraphQL requests can be sent from client to server: query to read data from the server and mutation to modify the data on the server. 

Arguments: Each GraphQL query/mutation can take arguments, which can be built-in scalar types (like String, Int) or complex objects (the fields of which are either other objects or scalar types). Arguments are used by the server, to either identify a resource or filter the response.

Variables: In a real application, the arguments to a request will be dynamic; GraphQL has a way to factor dynamic values out of the query, and pass them as a separate dictionary. These values are called variables, which are passed alongside the request.

Endpoint: GraphQL server operates on a single URL/endpoint, usually /graphql, and the GraphQL request is sent through a  Hypertext Transfer Protocol (HTTP)POST request. 

Introspection: It is useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system, where one can access the documentation about the type system in the schema which could be used for rich Integrated development environment  (IDE) experiences. GraphQL Playground is a good example of one of many applications that makes use of introspection to provide better development workflows while using GraphQL.

Let us focus on implementation.

Example -  to-do app

GraphQL is a specification and there are numerous implementations. Depending on your language/framework you should choose the relevant server library and GraphQL client.

Let us try to build the to-do list application using GraphQL. The requirements for this application are

● A user using this application can CRUD lists.

● A list can contain a set of Items.

● User can add/delete items to a list.

● User can move an item from one list to another list.

The final front-end of the application would look something like below

To-do list app implemented using GraphQL
Screenshot of to-do list app implemented using GraphQL

Let us choose Java Spring Boot to implement back-end and Angular to implement front-end for this app. The workflow will be somewhat the same across all frameworks. We will choose the Java GraphQL server library and Apollo Angular Client for integrating GraphQL. Depending on your application’s back-end and front-end frameworks, you will similarly choose the corresponding GraphQL server library and GraphQL client.

Back-end implementation — Java Spring Boot

 

Step 1 — Define GraphQL schema

GraphQL schema for the to-do list app
GraphQL schema for the to-do list app

This is our first step, here the schema describes two object types List and Item. The List object is composed of simple scalar types ( id, name, position) and also object types ( items). We have exposed one query to the client lists(positions:[Int]):[List] . This query accepts an optional parameter position, which is an array of Int, and returns an array of List objects in the response.

Along with this, we have exposed six mutation requests. The client can use these to perform CUD operations, which will modify the data on the server. For some of these mutation requests, we have defined an input type, which is an object type that is used to bundle required fields and passed as an argument to the requests being sent.

Step 2 — Implement GraphQLProvider

Implement GraphQLProvider, to initialize GraphQL. This is done by parsing the above schema file. Then, for each query in the schema, methods required to fetch the data are mapped. It could look as below.

GraphQLProvider initializing GraphQL configuration
GraphQLProvider initializing GraphQL configuration

 

We map the queries in our GraphQL schema with methods in GraphQLDataFetcher in buildWiring, which will get the relevant data by communicating with the service layer.

Step 3 — Implement GraphQL DataFetcher

Connect GraphQL with your service layer. It will contain methods, each of which returns a DataFetcher interface that will be wired into GraphQL in the buildWiring method mentioned above. For each request, GraphQL implementation will call a few of these methods to fulfill the query from the client. Here, it will look as follows.

Implementing GraphQL Data Fetcher
Implementing GraphQL Data Fetcher

 

The POST /graphql endpoint is the default endpoint, which the server uses to listen for GraphQL requests. At this point, you could use a tool like GraphQL playground to test the back-end API. 

Front-end Implementation — Angular Apollo

If we implemented all our components in the Angular application, we will use GraphQL to fetch the data from the back-end. The following steps are required to integrate GraphQL into our Angular services

Step 1 — Implement GraphQLModule

This NgModule contains all the configurations related to Angular Apollo GraphQL client. For our case, the implementation could look as follows -

GraphQLModule used for configuring GraphQL client
GraphQLModule used for configuring GraphQL client

Here, we import the essential Angular Apollo modules and initialize the Apollo client. The Apollo client takes in arguments that contain uri. The value of uri should point to the GraphQL endpoint of our server. The arguments also contain cache which is used for caching requests/response being sent through GraphQL on the client-side. We could disable this, which we did by passing the optional defaultOptions argument. We configured all the fetch requests to use network-only and not rely on the local cache.

Disabling cache is optional and not required. For our simple application, it doesn’t affect the performance. Caching in GraphQL is tricky and may not always work as we expect it to work. To get desired results with cache in a complex application, some fine-tuned configuration set-up effort might be necessary.

Step 2 — Import GraphQLModule into App Module

For our implementation, the AppModule will look as follows.

Main AppModule of the front-end of to-do list app
Main AppModule of the front-end of to-do list app

Here GraphQLModule being imported is the only relevant detail with respect to GraphQL. All other details are application-specific.

Step 3 — Import Apollo GraphQL Client into Angular Services

Our Angular services will now use this Apollo client to send GraphQL requests to the server and process the response and forward the relevant details to components, for updating the app view. Below are a couple of Angular services from our to-do list app showing the relevant implementation details.

Home Page Service which makes a query request to GraphQL server
Home Page Service which makes a query request to GraphQL server
List service which makes list mutation requests to GraphQL server
List service which makes list mutation requests to GraphQL server

We now have an app that will communicate with our server through GraphQL. 

High-Level data flow and architecture view

Summarizing, the architecture of the app we have implemented and data flow from client to server in this app. 

On front-end the Angular components which are responsible for rendering the view will call the Angular services whenever they require any data. The Angular services will then prepare a GraphQL request as per the required data and use Apollo GraphQL client to send this request to the server. At this point, an HTTP request containing the contents of the GraphQL query will be sent to the server from the client.

Once a GraphQL request is received at the server. The contents of the request are first validated against the schema. If the request sent is not conforming to the schema exposed by the server, a response containing the error message is immediately sent back. If the request is valid it will then trigger the various methods defined in GraphQLDataFetcher. Each method in the GraphQLDataFetcher will call the methods in the application service layer. These services will then make a database request and return the relevant data. After all the data required by a query has been aggregated. A response will be generated as per the request sent from the client. This response is then sent back to the client through HTTP. 

High-Level data flow in the application
High-Level data flow in the application

Links

●   The GitHub Repository containing the back-end implementation discussed in this  article can be found here.

●   The GitHub Repository containing the front-end implementation discussed in this article can be found here.

An earlier version of this blog was published on Medium by the author.

ZEMOSO ENGINEERING STUDIO

GraphQL with Java Spring Boot and Apollo Angular for Agile platforms

April 30, 2019
9 min read

GraphQL can be used with any backend framework or programming language. Lets first understand certain GraphQL concepts, before we learn how to implement it. 

Background — GraphQL concepts

Schema: GraphQL schema is a description of the data that clients can request from a GraphQL Application Programming Interface (API). A client could use this schema, and construct a request, to fetch/update the relevant data on the server. 

Queries and mutations: Two different types of GraphQL requests can be sent from client to server: query to read data from the server and mutation to modify the data on the server. 

Arguments: Each GraphQL query/mutation can take arguments, which can be built-in scalar types (like String, Int) or complex objects (the fields of which are either other objects or scalar types). Arguments are used by the server, to either identify a resource or filter the response.

Variables: In a real application, the arguments to a request will be dynamic; GraphQL has a way to factor dynamic values out of the query, and pass them as a separate dictionary. These values are called variables, which are passed alongside the request.

Endpoint: GraphQL server operates on a single URL/endpoint, usually /graphql, and the GraphQL request is sent through a  Hypertext Transfer Protocol (HTTP)POST request. 

Introspection: It is useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system, where one can access the documentation about the type system in the schema which could be used for rich Integrated development environment  (IDE) experiences. GraphQL Playground is a good example of one of many applications that makes use of introspection to provide better development workflows while using GraphQL.

Let us focus on implementation.

Example -  to-do app

GraphQL is a specification and there are numerous implementations. Depending on your language/framework you should choose the relevant server library and GraphQL client.

Let us try to build the to-do list application using GraphQL. The requirements for this application are

● A user using this application can CRUD lists.

● A list can contain a set of Items.

● User can add/delete items to a list.

● User can move an item from one list to another list.

The final front-end of the application would look something like below

To-do list app implemented using GraphQL
Screenshot of to-do list app implemented using GraphQL

Let us choose Java Spring Boot to implement back-end and Angular to implement front-end for this app. The workflow will be somewhat the same across all frameworks. We will choose the Java GraphQL server library and Apollo Angular Client for integrating GraphQL. Depending on your application’s back-end and front-end frameworks, you will similarly choose the corresponding GraphQL server library and GraphQL client.

Back-end implementation — Java Spring Boot

 

Step 1 — Define GraphQL schema

GraphQL schema for the to-do list app
GraphQL schema for the to-do list app

This is our first step, here the schema describes two object types List and Item. The List object is composed of simple scalar types ( id, name, position) and also object types ( items). We have exposed one query to the client lists(positions:[Int]):[List] . This query accepts an optional parameter position, which is an array of Int, and returns an array of List objects in the response.

Along with this, we have exposed six mutation requests. The client can use these to perform CUD operations, which will modify the data on the server. For some of these mutation requests, we have defined an input type, which is an object type that is used to bundle required fields and passed as an argument to the requests being sent.

Step 2 — Implement GraphQLProvider

Implement GraphQLProvider, to initialize GraphQL. This is done by parsing the above schema file. Then, for each query in the schema, methods required to fetch the data are mapped. It could look as below.

GraphQLProvider initializing GraphQL configuration
GraphQLProvider initializing GraphQL configuration

 

We map the queries in our GraphQL schema with methods in GraphQLDataFetcher in buildWiring, which will get the relevant data by communicating with the service layer.

Step 3 — Implement GraphQL DataFetcher

Connect GraphQL with your service layer. It will contain methods, each of which returns a DataFetcher interface that will be wired into GraphQL in the buildWiring method mentioned above. For each request, GraphQL implementation will call a few of these methods to fulfill the query from the client. Here, it will look as follows.

Implementing GraphQL Data Fetcher
Implementing GraphQL Data Fetcher

 

The POST /graphql endpoint is the default endpoint, which the server uses to listen for GraphQL requests. At this point, you could use a tool like GraphQL playground to test the back-end API. 

Front-end Implementation — Angular Apollo

If we implemented all our components in the Angular application, we will use GraphQL to fetch the data from the back-end. The following steps are required to integrate GraphQL into our Angular services

Step 1 — Implement GraphQLModule

This NgModule contains all the configurations related to Angular Apollo GraphQL client. For our case, the implementation could look as follows -

GraphQLModule used for configuring GraphQL client
GraphQLModule used for configuring GraphQL client

Here, we import the essential Angular Apollo modules and initialize the Apollo client. The Apollo client takes in arguments that contain uri. The value of uri should point to the GraphQL endpoint of our server. The arguments also contain cache which is used for caching requests/response being sent through GraphQL on the client-side. We could disable this, which we did by passing the optional defaultOptions argument. We configured all the fetch requests to use network-only and not rely on the local cache.

Disabling cache is optional and not required. For our simple application, it doesn’t affect the performance. Caching in GraphQL is tricky and may not always work as we expect it to work. To get desired results with cache in a complex application, some fine-tuned configuration set-up effort might be necessary.

Step 2 — Import GraphQLModule into App Module

For our implementation, the AppModule will look as follows.

Main AppModule of the front-end of to-do list app
Main AppModule of the front-end of to-do list app

Here GraphQLModule being imported is the only relevant detail with respect to GraphQL. All other details are application-specific.

Step 3 — Import Apollo GraphQL Client into Angular Services

Our Angular services will now use this Apollo client to send GraphQL requests to the server and process the response and forward the relevant details to components, for updating the app view. Below are a couple of Angular services from our to-do list app showing the relevant implementation details.

Home Page Service which makes a query request to GraphQL server
Home Page Service which makes a query request to GraphQL server
List service which makes list mutation requests to GraphQL server
List service which makes list mutation requests to GraphQL server

We now have an app that will communicate with our server through GraphQL. 

High-Level data flow and architecture view

Summarizing, the architecture of the app we have implemented and data flow from client to server in this app. 

On front-end the Angular components which are responsible for rendering the view will call the Angular services whenever they require any data. The Angular services will then prepare a GraphQL request as per the required data and use Apollo GraphQL client to send this request to the server. At this point, an HTTP request containing the contents of the GraphQL query will be sent to the server from the client.

Once a GraphQL request is received at the server. The contents of the request are first validated against the schema. If the request sent is not conforming to the schema exposed by the server, a response containing the error message is immediately sent back. If the request is valid it will then trigger the various methods defined in GraphQLDataFetcher. Each method in the GraphQLDataFetcher will call the methods in the application service layer. These services will then make a database request and return the relevant data. After all the data required by a query has been aggregated. A response will be generated as per the request sent from the client. This response is then sent back to the client through HTTP. 

High-Level data flow in the application
High-Level data flow in the application

Links

●   The GitHub Repository containing the back-end implementation discussed in this  article can be found here.

●   The GitHub Repository containing the front-end implementation discussed in this article can be found here.

An earlier version of this blog was published on Medium by the author.

Recent Publications
Actual access control without getting in the way of actual work: 2023
Actual access control without getting in the way of actual work: 2023
March 13, 2023
Breaking the time barrier: Test Automation and its impact on product launch cycles
Breaking the time barrier: Test Automation and its impact on product launch cycles
January 20, 2023
Product innovation for today and the future! It’s outcome-first, timeboxed, and accountable
Product innovation for today and the future! It’s outcome-first, timeboxed, and accountable
January 9, 2023
From "great potential" purgatory to "actual usage" reality: getting SDKs right in a product-led world
From "great potential" purgatory to "actual usage" reality: getting SDKs right in a product-led world
December 6, 2022
Why Realm trumps SQLite as database of choice for complex mobile apps — Part 2
Why Realm trumps SQLite as database of choice for complex mobile apps — Part 2
October 13, 2022
Testing what doesn’t exist with a Wizard of Oz twist
Testing what doesn’t exist with a Wizard of Oz twist
October 12, 2022
Docs, Guides, Resources: Getting developer microsites right in a product-led world
Docs, Guides, Resources: Getting developer microsites right in a product-led world
September 20, 2022
Beyond methodologies: Five engineering do's for an agile product build
Beyond methodologies: Five engineering do's for an agile product build
September 5, 2022
Actual access control without getting in the way of actual work: 2023
Actual access control without getting in the way of actual work: 2023
March 13, 2023
Breaking the time barrier: Test Automation and its impact on product launch cycles
Breaking the time barrier: Test Automation and its impact on product launch cycles
January 20, 2023
Product innovation for today and the future! It’s outcome-first, timeboxed, and accountable
Product innovation for today and the future! It’s outcome-first, timeboxed, and accountable
January 9, 2023
From "great potential" purgatory to "actual usage" reality: getting SDKs right in a product-led world
From "great potential" purgatory to "actual usage" reality: getting SDKs right in a product-led world
December 6, 2022
Why Realm trumps SQLite as database of choice for complex mobile apps — Part 2
Why Realm trumps SQLite as database of choice for complex mobile apps — Part 2
October 13, 2022
ZEMOSO ENGINEERING STUDIO
April 30, 2019
9 min read

GraphQL with Java Spring Boot and Apollo Angular for Agile platforms

GraphQL can be used with any backend framework or programming language. Lets first understand certain GraphQL concepts, before we learn how to implement it. 

Background — GraphQL concepts

Schema: GraphQL schema is a description of the data that clients can request from a GraphQL Application Programming Interface (API). A client could use this schema, and construct a request, to fetch/update the relevant data on the server. 

Queries and mutations: Two different types of GraphQL requests can be sent from client to server: query to read data from the server and mutation to modify the data on the server. 

Arguments: Each GraphQL query/mutation can take arguments, which can be built-in scalar types (like String, Int) or complex objects (the fields of which are either other objects or scalar types). Arguments are used by the server, to either identify a resource or filter the response.

Variables: In a real application, the arguments to a request will be dynamic; GraphQL has a way to factor dynamic values out of the query, and pass them as a separate dictionary. These values are called variables, which are passed alongside the request.

Endpoint: GraphQL server operates on a single URL/endpoint, usually /graphql, and the GraphQL request is sent through a  Hypertext Transfer Protocol (HTTP)POST request. 

Introspection: It is useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system, where one can access the documentation about the type system in the schema which could be used for rich Integrated development environment  (IDE) experiences. GraphQL Playground is a good example of one of many applications that makes use of introspection to provide better development workflows while using GraphQL.

Let us focus on implementation.

Example -  to-do app

GraphQL is a specification and there are numerous implementations. Depending on your language/framework you should choose the relevant server library and GraphQL client.

Let us try to build the to-do list application using GraphQL. The requirements for this application are

● A user using this application can CRUD lists.

● A list can contain a set of Items.

● User can add/delete items to a list.

● User can move an item from one list to another list.

The final front-end of the application would look something like below

To-do list app implemented using GraphQL
Screenshot of to-do list app implemented using GraphQL

Let us choose Java Spring Boot to implement back-end and Angular to implement front-end for this app. The workflow will be somewhat the same across all frameworks. We will choose the Java GraphQL server library and Apollo Angular Client for integrating GraphQL. Depending on your application’s back-end and front-end frameworks, you will similarly choose the corresponding GraphQL server library and GraphQL client.

Back-end implementation — Java Spring Boot

 

Step 1 — Define GraphQL schema

GraphQL schema for the to-do list app
GraphQL schema for the to-do list app

This is our first step, here the schema describes two object types List and Item. The List object is composed of simple scalar types ( id, name, position) and also object types ( items). We have exposed one query to the client lists(positions:[Int]):[List] . This query accepts an optional parameter position, which is an array of Int, and returns an array of List objects in the response.

Along with this, we have exposed six mutation requests. The client can use these to perform CUD operations, which will modify the data on the server. For some of these mutation requests, we have defined an input type, which is an object type that is used to bundle required fields and passed as an argument to the requests being sent.

Step 2 — Implement GraphQLProvider

Implement GraphQLProvider, to initialize GraphQL. This is done by parsing the above schema file. Then, for each query in the schema, methods required to fetch the data are mapped. It could look as below.

GraphQLProvider initializing GraphQL configuration
GraphQLProvider initializing GraphQL configuration

 

We map the queries in our GraphQL schema with methods in GraphQLDataFetcher in buildWiring, which will get the relevant data by communicating with the service layer.

Step 3 — Implement GraphQL DataFetcher

Connect GraphQL with your service layer. It will contain methods, each of which returns a DataFetcher interface that will be wired into GraphQL in the buildWiring method mentioned above. For each request, GraphQL implementation will call a few of these methods to fulfill the query from the client. Here, it will look as follows.

Implementing GraphQL Data Fetcher
Implementing GraphQL Data Fetcher

 

The POST /graphql endpoint is the default endpoint, which the server uses to listen for GraphQL requests. At this point, you could use a tool like GraphQL playground to test the back-end API. 

Front-end Implementation — Angular Apollo

If we implemented all our components in the Angular application, we will use GraphQL to fetch the data from the back-end. The following steps are required to integrate GraphQL into our Angular services

Step 1 — Implement GraphQLModule

This NgModule contains all the configurations related to Angular Apollo GraphQL client. For our case, the implementation could look as follows -

GraphQLModule used for configuring GraphQL client
GraphQLModule used for configuring GraphQL client

Here, we import the essential Angular Apollo modules and initialize the Apollo client. The Apollo client takes in arguments that contain uri. The value of uri should point to the GraphQL endpoint of our server. The arguments also contain cache which is used for caching requests/response being sent through GraphQL on the client-side. We could disable this, which we did by passing the optional defaultOptions argument. We configured all the fetch requests to use network-only and not rely on the local cache.

Disabling cache is optional and not required. For our simple application, it doesn’t affect the performance. Caching in GraphQL is tricky and may not always work as we expect it to work. To get desired results with cache in a complex application, some fine-tuned configuration set-up effort might be necessary.

Step 2 — Import GraphQLModule into App Module

For our implementation, the AppModule will look as follows.

Main AppModule of the front-end of to-do list app
Main AppModule of the front-end of to-do list app

Here GraphQLModule being imported is the only relevant detail with respect to GraphQL. All other details are application-specific.

Step 3 — Import Apollo GraphQL Client into Angular Services

Our Angular services will now use this Apollo client to send GraphQL requests to the server and process the response and forward the relevant details to components, for updating the app view. Below are a couple of Angular services from our to-do list app showing the relevant implementation details.

Home Page Service which makes a query request to GraphQL server
Home Page Service which makes a query request to GraphQL server
List service which makes list mutation requests to GraphQL server
List service which makes list mutation requests to GraphQL server

We now have an app that will communicate with our server through GraphQL. 

High-Level data flow and architecture view

Summarizing, the architecture of the app we have implemented and data flow from client to server in this app. 

On front-end the Angular components which are responsible for rendering the view will call the Angular services whenever they require any data. The Angular services will then prepare a GraphQL request as per the required data and use Apollo GraphQL client to send this request to the server. At this point, an HTTP request containing the contents of the GraphQL query will be sent to the server from the client.

Once a GraphQL request is received at the server. The contents of the request are first validated against the schema. If the request sent is not conforming to the schema exposed by the server, a response containing the error message is immediately sent back. If the request is valid it will then trigger the various methods defined in GraphQLDataFetcher. Each method in the GraphQLDataFetcher will call the methods in the application service layer. These services will then make a database request and return the relevant data. After all the data required by a query has been aggregated. A response will be generated as per the request sent from the client. This response is then sent back to the client through HTTP. 

High-Level data flow in the application
High-Level data flow in the application

Links

●   The GitHub Repository containing the back-end implementation discussed in this  article can be found here.

●   The GitHub Repository containing the front-end implementation discussed in this article can be found here.

An earlier version of this blog was published on Medium by the author.

Recent Publications

ZEMOSO ENGINEERING STUDIO

Testing what doesn’t exist with a Wizard of Oz twist

October 12, 2022
7 min read
ZEMOSO ENGINEERING STUDIO

Beyond methodologies: Five engineering do's for an agile product build

September 5, 2022
6 min read
ZEMOSO ENGINEERING STUDIO

How we built a big data platform for a futuristic AgriTech product

June 3, 2022
8 min read
ZEMOSO NEWS

Zemoso Labs starts operations in Waterloo, Canada

May 25, 2022
5 min read
ZEMOSO ENGINEERING STUDIO

Honorable mention at O’Reilly’s Architectural Katas event

May 17, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

Product dev with testable spring boot applications, from day one

May 4, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

When not to @Autowire in Spring or Spring Boot applications

May 1, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

Efficiently handle data and integrations in Spring Boot

January 24, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

Our favorite CI/CD DevOps Practice: Simplify with GitHub Actions

October 25, 2020
5 min read
ZEMOSO ENGINEERING STUDIO

How to use BERT and DNN to build smarter NLP algorithms for products

February 14, 2020
12 min read
ZEMOSO ENGINEERING STUDIO

GraphQL — Why is it essential for agile product development?

April 30, 2019
12 min read
ZEMOSO ENGINEERING STUDIO

Deploying Airflow on Kubernetes 

November 30, 2018
2 min read
ZEMOSO PRODUCT STUDIO

How to validate your Innovation: Mastering Experiment Design

November 22, 2018
8 min read
ZEMOSO PRODUCT STUDIO

Working Backwards: Amazon's Culture of Innovation: My notes

November 19, 2018
8 min read
ZEMOSO ENGINEERING STUDIO

Product developer POV: Caveats when building with Spark

November 5, 2018
2 min read

Want more best practices?

Access thought-leadership and best practice content across
the product development lifecycle

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

© 2023  Zemoso Technologies
Privacy Policy

Terms of Use
LinkedIn Page - Zemoso TechnologiesFacebook Page - Zemoso TechnologiesTwitter Account - Zemoso Technologies

© 2021 Zemoso Technologies
Privacy Policy

LinkedIn Page - Zemoso TechnologiesFacebook Page - Zemoso TechnologiesTwitter Account - Zemoso Technologies
May 25, 2023