Microservices architecture, which was relatively avant-garde just a few years ago, is everywhere. Thanks to the support of the open source community, we can easily prototype a microservice architecture using Spring Cloud and Docker container. No matter mature Internet companies, startups or individual developers, the acceptance of micro-service architecture is quite high. The wide application of micro-service architecture also naturally promotes the better development and more practice of technology itself. Based on the project practice, this paper will analyze how to develop mobile applications by separating the front and back ends under the background of microservices.

For microservices themselves, we can refer to Martin Fowler’s description of microservices. To put it simply, microservices are an architectural style. By analyzing and modeling specific business domains, complex applications are decomposed into small, specialized, poorly coupled, and highly autonomous sets of services. Each service in a microservice is a small application that is independent and deployable from each other. Microservices achieve the purpose of simplifying applications by splitting complex applications, while these services with low coupling degree communicate with each other in the form of API, so all exposed between services are APIS, no matter the acquisition or modification of resources.

The concept of microservice architecture is consistent with the concept of the separation of front and back ends, where the front-end application controls all its UI level logic, while the data level is completed through API calls to the microservice system. JSP (Java Server Pages) as the representative of the front and back end interaction also gradually withdrew from the historical stage. The rapid development of front-end separation also benefits from the continuous emergence of front-end Web frameworks (Angular, React, etc.). Single Page Application has rapidly become a standard paradigm of front-end development. In addition, with the development of Mobile Internet, both Mobile Native development mode and Hybrid application development mode represented by React Native/PhoneGap make Web and Mobile applications become clients. The client only needs to query and modify resources through THE API.


Overview and evolution of BFF

Backend for Frontends(BFF), as its name implies, is the back-end (service) intermediate layer for the front-end. That is, in the traditional separated application, the front-end application directly invokes the back-end service, and the back-end service adds, deletes, checks, and modifies data based on the relevant service logic. After referencing the BFF, the front-end application communicates directly with the BFF, which in turn communicates with the back-end API, so in essence, BFF is more of a “middle tier” service. The figure below shows the major differences between the front and back end projects without BFF and with BFF.

1. Front and back end architecture without BFF

In the traditional front-end and back-end design, the App or Web side usually directly accesses the back-end services, and the back-end micro-services call each other, and then return the final results for the front-end consumption. Excessive HTTP requests are expensive for clients (especially mobile), so to minimize the number of requests during development, the front-end tends to pull the associated data through an API. In microservice mode, this means that sometimes the server will do some uI-related logic to meet the needs of the client.

2. Added the front and back end architecture of BFF

The biggest difference is that the front-end (Mobile, Web) no longer accesses the back-end microservices directly, but accesses them through the BFF layer. And each client has a BFF service. From a microservice perspective, with BFF, there are fewer calls between microservices. This is because some of the UI logic is processed at the BFF layer.

BFF and API Gateway

What is the difference between BFF and an API Gateway since BFF is a mid-tier service for front and back end access? Let’s start by looking at a common implementation of the API Gateway. (API Gateway can be designed in many ways, but here are just three.)

1. The first implementation of the API Gateway: an API Gateway provides the same API for all clients

A single API Gateway instance provides the same API service for multiple clients. In this case, the API Gateway does not distinguish between client types. That is, all/API /users processing is consistent, and the API Gateway does not make any distinction. As shown below:

2. The second implementation of the API Gateway: an API Gateway provides a separate API for each client

A single API Gateway instance that provides different apis for multiple clients. Access control list for the users, for example, a web client and App side respectively through/services/mobile/API/users, service/services/web/API/users. The API Gateway determines which clients the different apis come from, and then processes them separately to return the resources required by the different clients.

3. A third implementation of the API Gateway: Multiple API gateways provide separate apis for each client

Under this implementation, a separate API Gateway responds to EACH type of client’s API requests. So BFF is actually one of the implementation patterns of the API Gateway.


GraphQL with REST

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.

GraphQL was introduced by Facebook in 2015 as an API query to replace REST. What are the differences between GraphQL and REST? We can understand this through the following example.

By REST design standards, all access is based on access to resources (add, delete, change). If you access the Users resources in your system, REST may access them in the following way:

Request:

GET http://localhost/api/usersCopy the code

Response:

[
  {
    "id": 1,
    "name": "abc",
    "avatar": "http://cdn.image.com/image_avatar1"
  },
  ...
]Copy the code

For the same request, if accessed with GraphQL, the process is as follows:

Request:

POST http://localhost/graphqlCopy the code

Body:

query {users { id, name, avatar } }Copy the code

Response:

{ "data": { "users": [ { "id": 1, "name": "abc", "avatar": "http://cdn.image.com/image_avatar1" }, ... ] }}Copy the code

For more detailed usage of GraphQL, check out the documentation and other articles. Compared to REST style, GraphQL has the following features:

1. Define the data model: capture on demand

On the server implementation side, GraphQL needs to define different data models. All access from the front end is ultimately mapped and parsed through the data model defined by the GraphQL back end. And this model-based definition is on-demand. For example, for the above /users resource, if the client only cares about user.id, user.name information. Users {id \n name} is passed to query when called by the client. The model is defined in the background, and the client only needs to get the data it cares about.

2. Data layering

To query a group of users data, it may be necessary to obtain user.friends, user.friends.addr and other information. Therefore, this query for users actually involves user, Frind and ADDR data respectively. GraphQL queries hierarchical data, greatly reducing the number of client requests. In REST mode, this might mean that each time the ‘user’ data is fetched, the API needs to be sent again to request the Friends interface. GraphQL, by layering data, lets clients get all the data they need from a single API. This is where GraphQL (Graph Query Language) comes from.

{user(id:1001) {// layer 1 name, friends {// layer 2 name, addr {// layer 3 country, city}}}}Copy the code

3. Strong type

const Meeting = new GraphQLObjectType({
  name: 'Meeting',
  fields: () => ({
    meetingId: {type: new GraphQLNonNull(GraphQLString)},
    meetingStatus: {type: new GraphQLNonNull(GraphQLString), defaultValue: ''}
  })
})Copy the code

The GraphQL type system defines data types including Int, Float, String, Boolean, ID, Object, List, non-NULL, and so on. Therefore, in the development process, the use of powerful strong type checking, can greatly save the development time, but also very convenient for the front and back end debugging.

4. Protocol, not storage

GraphQL itself does not directly provide back-end storage capabilities; it is not tied to any database or storage engine. It uses existing code and technology to manage data sources. For example, using GraphQL at the BFF layer does not require any database or storage medium. GraphQL simply parses the client request, knows the “intent” of the client, and then obtains data through access to the microservice API for a series of assembly or filtering of the data.

5. Versioning is not required

const PhotoType = new GraphQLObjectType({
  name: 'Photo',
  fields: () => ({
    photoId: {type: new GraphQLNonNull(GraphQLID)},
    file: {
      type: new GraphQLNonNull(FileType),
      deprecationReason: 'FileModel should be removed after offline app code merged.',
      resolve: (parent) => {
        return parent.file
      }
    },
    fileId: {type: new GraphQLNonNull(GraphQLID)}
  })
})Copy the code

The GraphQL server can automatically flag a field as deprecated by adding deprecationReason. And because of the high scalability of GraphQL, if you don’t need a certain data, you just need to use a new field or structure. The old fields are discarded to provide services for the old clients, and all new clients use the new fields to obtain relevant information. Considering that all graphQL requests are sent according to POST/GraphQL, versioning is not required in GraphQL.

GraphQL with REST

There are major differences between GraphQL and REST:

1. Data fetching: REST lacks scalability and GraphQL can be fetched on demand. When the GraphQL API is called, payload is extensible;

2. API calls: REST has an endpoint for each resource. GraphQL requires only one endpoint(/ GraphQL), but the post body is different.

3. Complex data request: REST requires multiple calls for nested complex data, while GraphQL calls once to reduce network overhead;

4. Error code processing: REST can accurately return HTTP error code, GraphQL uniformly returns 200, packaging error information;

5. Version number: REST is implemented by V1 / V2, GraphQL is implemented by Schema extension;


Microservices + GraphQL + BFF practice

Building BFF based on GraphQL under microservices is a practice we have already started in our project. In the corresponding business scenario of our project, there are nearly 10 micro-services in the background, and the clients include 4 APPS for different roles and one Web terminal. There is a BFF for each type of App. Each BFF only serves this App. After the BFF parses the request to the client, it will be discovered through the service of the BFF end and go to the corresponding micro-service background to query or modify the data in the way of CQRS.

1. BFF end technology stack

We used the GraphQL-Express framework to build the BFF side of the project and then deployed it via Docker. Registrator and Consul are still used for service registration and discovery between BFF and microservice backend.

 addRoutes () {
    this.express.use('/graphql', this.resolveFromRequestScopeAndHandle('GraphqlHandler'))
    this.serviceNames.forEach(serviceName => {
      this.express.use(`/api/${serviceName}`, this.routers.apiProxy.createRouter(serviceName))
    })
  }Copy the code

/ graphQL and/API /${serviceName} are two parts of the BFF route setup. / graphQL processes all graphQL query requests. At the same time, we added/API /${serviceName} on the BFF side for API pass-through. For some requests that do not need to be encapsulated by GraphQL, they can be directly accessed to the related microservices through pass-through.

2. Overall technical architecture

Overall, our front and back end architecture diagram is as follows. Three App clients request corresponding BFF in the form of GraphQL respectively. The BFF layer then discovers and communicates with the back end via Consul.

About authentication in the system

After the user logs in, the App accesses the KeyCloak service to obtain the ID_token. The access_token is then obtained by accessing auth-API service through ID_token passthrough. The access_token is placed in the header of subsequent HTTP requests in the form of JWT (Json Web Token).

In our system, BFF layer does not provide authentication services, and all authentication processes are taken charge of by their respective microservice modules. BFF only provides the function of transfer. Whether BFF requires integrated authentication depends on the design of each system and is not a standard practice.

3. GraphQL + BFF practice

Some of the better features of graphQL-based BFF can be considered in the following ways:

GraphQL and BFF focus on business points

In terms of business, PM App (user: property manager) focuses on property. The property manager manages a batch of houses, so he needs to know the general situation of all houses and whether there is a corresponding maintenance application for each house. So PM App BFF defines data structures. MaintemamceRequests are child attributes of property.

For similar data, the Supplier App focuses on the maintenanceRequest. Therefore, the main body of the data obtained by the Supplier App is the maintenanceRequest. Maintenance supplier of workOrder. MaintenanceRequest.

So different clients, because there are different usage scenarios, have different concerns for the same data. BFF is pary of Application. From this perspective, the data structures defined in the BFF are what the client really cares about. BFF was born for the client and is part of the client. It is important to note that “business concerns” does not mean that BFF handles all business logic. Business logic should still be taken care of by microservices. BFF focuses on what the client needs.

GraphQL support for versioning

Assume that the BFF side has been published to the production environment, providing look-throughs of inspection-related tenants and landlords. Now we need to change the structure of Figure 1 to figure 2, but in order not to affect the API access of old users, our BFF API must be compatible at this time. In REST, API/V2 / CONFORMS to might be added for API upgrade. In BFF, however, we can use the structure shown in Figure 3 for forward compatibility. At this point, the old APP uses the data structure of the yellow area, while the new APP uses the structure defined by the blue area.

GraphQL Mutation and CQRS

mutation {
  area {
    create (input: {
      areaId:"111", 
      name:"test", 
    })
  }
}Copy the code

If you read the GraphQL documentation in detail, you can see that GraphQL separates Query and mutation. All queries should use query {… }, the corresponding mutaition needs to use mutation {… }. While it may seem like a convention, this GraphQL design coincides with Command Query Responsibility Segregation of back-end apis. In fact, we use it in accordance with the specification. All mutation calls the background API, and the backend API changes resources in the CQRS mode implemented by SpringBoot EventListener.

How to Do a good test

In the project where BFF was introduced, our tests still use the pyramid principle, but we need to add BFF tests between the client and the backend.

  • The integration-test of the Client is concerned with the connectivity of the App to the BFF. All requests from the App to access the BFF need to be tested.

  • The integration-test of BFF tests the connectivity of BFF to microservice apis. All apis that depend on BFF should be guaranteed by integration testing.

  • The integration-test of the API focuses on all the apis exposed by the service and typically tests all the apis in the Controller.


conclusion

Building BFF based on GraphQL in microservices isn’t a silver bullet, and it’s not necessarily suitable for every project — you might have query performance issues after using GraphQL, for example — but it’s a good try. You can see Facebook already uses GraphQL, and Github has opened up the GraphQL API. BFF, however, is something that many teams have already implemented. In special scenarios such as microservices, GraphQL + BFF may surprise your project.


At the upcoming Tech Radar Summit 2018, James Lewis, co-creator of the concept of “micro services”, will talk about the development trend and project practice of micro services. Sign up now (get 30% off early bird discount) and get the opportunity to discuss with The Big Bull

– Related reading –

Interpret GraphQL | insights

The microservices architecture case for Serverless

Click on insight for links to the original text & green font section.

The copyright of this article belongs to ThoughtWorks, Inc.