また、スキーマファーストな開発については先日開催された Builderscon で弊社の南が発表した「Web API に秩序を与える Protocol Buffers / Protocol Buffers for Web API #builderscon」と密接に関係している内容ですので、そちらのスライドも合わせて見てもらえるとより理解が深まるかと思います。
バックエンドからGraphQLサーバーまでの型の安全性が担保することができるようになりましたが、もちろんこの型定義はフロントでも共有したい情報です。こちらは Apollo の機能を使って実現しています。フロントエンドから、GraphQLサーバーに対して Introspection というタイプのリクエストを投げることにより、GraphQLサーバーからスキーマファイルをダウンロードすることができます。
$ apollo client:download-schema --endpoint ${GRAPHQL_ENDPOINT}
さらに Apollo を使って、このスキーマファイルとソースコード内に書かれた Query/Mutation から TS の型定義ファイルを生成を行うことができます。
$ apollo client:codegen --localSchemaFile=/path/to/schema_file --target=typescript --includes='./src/**/*.{ts,tsx,graphql}' --watch
Hello. It is Hara ( chloe463 ) who is a Frontend Engineer in the Product Squad of Wantedly Visit . Product Squad mainly develops and renews the Web version of Wantedly Visit. In this article, I will introduce the development of the GraphQL server that was introduced in the development of the recruitment creation screen that was just released the other day.
We used Nexus, which allows code-first schema definition for GraphQL server development .
Generating client files and type definition files from API definitions (swagger and protobuf) ensures the type safety from backend to frontend.
Background of GraphQL server introduction as BFF
The Wantedly Visit service itself is a monolithic Rails application. Despite regular debt repayments, long-running applications are enormous and have technical strata that reduce productivity. Therefore, the Wantedly Visit development team is currently promoting microservices, and from the beginning of this year we have been promoting the renewal of the recruitment creation screen, which is one of the core functions of Wantedly Visit. On the new recruitment creation screen, not only the appearance but also the technology used has been revamped. In particular...
The front end is SPA (New!) using React.
GraphQL server as BFF (New!)
API server related to recruitment creation (New!)
Wantedly Visit API Server
It became a structure. This time, we introduced BFF (Backend For Frontend) using GraphQL as a new approach. As a background to the introduction, for the backend API, which will continue to increase as microservices continue to be made, requests are made with a uniform interface without handling which service is requested on the frontend side. Because I wanted it. The overall picture is as shown in the figure below.
Schema-first development
First of all, as a premise, new front end development in Wantedly is done using TypeScript. The SPA created this time also implements the React application with TypeScript. GraphQL server was developed using TypeScript and Apollo server.
A major feature of GraphQL is that you can define types for data related to API request and response . In this development, we first started with the definition of Query/Mutation and the type definition of each Field, then developed the mock response, and finally implemented this Resolver. By preparing a mock response, you can proceed with front-end development without waiting for the API implementation behind the GraphQL server. Also, in this development, we adopted Nexus, which can generate GraphQL type files and TypeScript type definition files in code first . With Nexus, you can generate GraphQL schema definitions and TypeScript type definition files from code. Below is an excerpt from the actual code.
Note that there is no NexusGenRootTypes["Project"]["summaryTags"] in the TypeScript type files generated by Nexus . (Point of attention ①) This is because "summaryTags" describes individual resolve definitions in the type definition of Project . The project resolver is expected to return a GraphQL type world Project, a TypeScript world NexusGenRootTypes["Project"] . If the NexusGenRootTypes["Project"] has "summaryTags" defined, this resolver type error will occur. (I want you to return summaryTags, but if it is not in the return value, it will be an error because it does not match.) When returning data with a certain query, the main data is obtained from service A, but certain fields are service I think there is a use case where I want to get from B. The good part of Nexus is that it produces good types that take that into account.
As mentioned above, using GraphQL, it is possible to define types for all fields of objects related to request/response, but that is a benefit that the client who actually makes the API call receives, and the type check does not work during development. .. Even if you do your best to define the schema file, it is possible that you will accidentally write code that will return different types of values during development. However, if you use Nexus, a schema file and a type definition file of TS will be generated, and the type check for all fields will work during development, so you can prevent bugs from entering due to different types. Using a text editor that is strong against TS such as VSCode will perform this type check in real time, which is very helpful.
Regarding the schema-first development, it is closely related to " Protocol Buffers / Protocol Buffers for Web API #builderscon " that we announced at the Builderscon held the other day , which gives order to the Web API . I think that you can deepen your understanding if you also see the slides of.
Maintaining cross-service types
With Nexus, we were able to ensure type safety within our GraphQL server. To further strengthen this, API client file generation and TS type definition generation are performed from the back-end API definition file. As mentioned earlier, there are two API servers that will be the GraphQL backend introduced this time.
Wantedly visit main service API server (Restful API)
API server related to recruitment creation (protobuf over HTTP)
And each of these has a swagger file that describes the API definition and a proto file. Using these, client class and type definition file are generated from swagger file through openapi-generator and client class and type definition file from proto file through grpc tool .
As a result, when mapping the response from the backend as a GraphQL response, you can now perform type inconsistency checks and field excess/shortage checks.
The type safety from the backend to the GraphQL server can be secured, but of course this type definition is the information that we want to share at the front as well. This is achieved by using the function of Apollo. You can download the schema file from the GraphQL server from the front end by making a request of type Introspection to the GraphQL server.
$ apollo client:download-schema --endpoint ${GRAPHQL_ENDPOINT}
In addition, Apollo can be used to generate a TS type definition file from this schema file and the Query/Mutation written in the source code.
$ apollo client:codegen --localSchemaFile=/path/to/schema_file --target=typescript --includes='./src/**/*.{ts,tsx,graphql}' --watch
By adding the watch option, the type definition will be updated immediately when the query in the source code is rewritten. When developing locally, you can develop it without being aware of the type file generation by always executing this. It is a very good development experience.
By doing so, we were able to share the API type definition on the backend API server-GraphQL server-frontend service. Having the type improves the development experience by getting support from the IDE, and I think that checking the null values of each value becomes strict and leading to the prevention of bugs.
Front side implementation
Front side uses React. The newly created recruitment creation screen uses hooks entirely. That's because React 16.8.0 was released with react-hooks just when we started development. We also use hooks to call GraphQL query/mutation. (Use react-apollo-hooks. Currently planning to move to @apollo/react-hooks) As for state management, we also use state management using apollo-link instead of Redux. When calling Query, we have defined a fragment in each component, aggregate it as a query in the parent component, and use useQuery to obtain the data.
The type passed to generics of useQuery is the type generated from query/fragment etc. in the code. Since apollo generates a type from the schema definition and query, each Component can define that type as props and safely receive it while retaining the type information from its parent. I'll post another blog for more info on the frontend implementation and hooks...!
Summary
We introduced GraphQL server in order to promote the microservices of Wantedly Visit, and adopted nexus for its development that allows code-first development. In addition, code generation from swagger and protobuf improves type safety. Currently, other features are being renewed, but I feel that the speed of development has also increased by being able to generate code and type files in the same way. Developing with GraphQL is difficult because there are still trial and error, but that is also a fun part. We would like to share the knowledge of GraphQL development, so if you are interested, let's talk about it at in-house study sessions!
Wantedly, Inc. is looking for colleagues to work with
0 コメント:
コメントを投稿