Why FastAPI and gRPC?


We used Django to develop overseas financial products in the early stage of our team. Later, we decided to choose FastAPI and gRPC after some research, considering the transition to micro-service architecture.

FastAPI

Completely from the framework of asynchronous IO thinking integration, in the field of Web asynchronous IO is more significant. A new product based on Encode, the team that developed the famous Django REST Framework: Starlette.

gRPC

In RPC block, Python also has a famous framework nameko, which the team used before. However, due to the low domestic usage rate, it was difficult to solve the problem. Although Nameko API is very elegant, gRPC was finally selected for stable. GRPC is also a standard configuration in the cloud native era, with guaranteed popularity.

The ground practice


After a brief overview of DDD thinking, the team clapped their heads and began to dismantle the service. A total of eight services were split, each of which also provided HTTP/RPC services.

Flask framework FastAPI has the flexibility of micro-framework, but also this flexibility, so that the team of developers with different technical level to write a variety of project structures, cross-maintenance projects, very headache. Therefore, in order to unify and simplify the development of FastAPI & gRPC, the team iterated out an integrated framework: Bali, and the business code was distributed in two layers: Model layer and Resource layer. The Model layer is a Fat Model with some logic associated with the Model in addition to the field definitions. The Resource layer provides HTTP/ RPC-compatible Resource services. Following RESTful and gRPC development guidelines defines several standard methods.

Bali-core PIP install bali-coreCopy the code

The project structure

Only two services are painted here as practical examples, the user service and the order service, each of which is a separate repO, with a separate Proto repo dedicated to storing the Protobufs that gRPC needs to use.

# the user repo ├ ─ ─ the user │ ├ ─ ─ Dockerfile │ ├ ─ ─ the README. Md │ ├ ─ ─ clients │ │ ├ ─ ─ just set py │ │ ├ ─ ─ _config. Py │ │ ├ ─ ─ _utils. Py │ │ ├── intermediates │ ├─ core │ │ ├─ config.py │ │ ├── constants │ ├ ─ ─ main. Py │ ├ ─ ─ models │ │ ├ ─ ─ just set py │ │ ├ ─ ─ user. Py │ ├ ─ ─ requirements. TXT │ ├ ─ ─ requirements_dev. TXT │ ├ ─ ─ Resources │ │ ├ ─ ─ just set py │ │ ├ ─ ─ user. Py │ ├ ─ ─ schemas │ │ ├ ─ ─ just set py │ │ ├ ─ ─ user. Py │ ├ ─ ─ services │ │ ├ ─ ─ Just set py │ │ ├ ─ ─ the py │ │ ├ ─ ─ HTTP │ │ │ ├ ─ ─ users. Py │ │ └ ─ ─ the RPC │ │ ├ ─ ─ service. Py │ ├ ─ ─ tests │ │ ├ ─ ─ just set py │ │ ├ ─ ─ test_models │ │ ├ ─ ─ test_resources │ │ └ ─ ─ test_services # order repo ├ ─ ─ the user │ ├ ─ ─ Dockerfile │ ├ ─ ─ the README, md │ ├ ─ ─ clients │ │ ├ ─ ─ just set py │ │ ├ ─ ─ _config. Py │ │ ├ ─ ─ _utils. Py │ │ ├ ─ ─ intermediates │ ├ ─ ─ the core │ │ ├ ─ ─ Config. Py │ │ ├ ─ ─ constants │ │ ├ ─ ─ environment. Py │ │ ├ ─ ─ logging. Py │ ├ ─ ─ main. Py │ ├ ─ ─ models │ │ ├ ─ ─ just set py │ │ ├ ─ ─ user. Py │ ├ ─ ─ requirements. TXT │ ├ ─ ─ requirements_dev. TXT │ ├ ─ ─ resources │ │ ├ ─ ─ just set py │ │ ├ ─ ─ user. Py │ ├ ─ ─ Schemas │ │ ├ ─ ─ just set py │ │ ├ ─ ─ user. Py │ ├ ─ ─ services │ │ ├ ─ ─ just set py │ │ ├ ─ ─ the py │ │ ├ ─ ─ HTTP │ │ │ ├ ─ ─ Users. Py │ │ └ ─ ─ the RPC │ │ ├ ─ ─ service. Py │ ├ ─ ─ tests │ │ ├ ─ ─ just set py │ │ ├ ─ ─ test_models │ │ ├ ─ ─ test_resources │ │ ├── unlawfully, unlawfully, unlawfully, unlawfully, unlawfully, unlawfully, unlawfullyCopy the code

As you can see, the structure of the microservices is basically the same, with the business of the User service and the Order service housed primarily in Models and Resources.

The HTTP service

# user/services/http/users.py

@router.get("/users/", response_model=UserSchema)
def get_users(
    *,
    channel : str = Header(None),
) -> Any:
    return User.get_users()
Copy the code

This is FastAPI’s own route definition, which Bali is compatible with.

# user/resources/user.py

class UserResource(Resource):
    schema = UserSchema

    @action()
    def custome_users(self, schema_in):
        User.get_customer_users()
Copy the code

Resouce supports HTTP and RPC. Generic actions such as GetUsers do not need to be defined in Resource; they are already implemented in the Resource base class.

Start the HTTP service:

python main.py --http
Copy the code

The RPC service

  1. Define a Protobuf file
# user/services/rpc/user.proto
Copy the code

To compile the proto file, follow the method provided by gRPC:

$ python -m grpc_tools.protoc -I.. /.. /protos --python_out=. --grpc_python_out=. .. /.. /protos/user.protoCopy the code

With bali-CLI (the CLI tool that comes with the Bali framework), you just need to execute in the project root directory:

pip install bali-cli
bali build 
Copy the code

In conjunction with CI/CD, user.proto of the user service is automatically added to proto repo.

  1. GRPC implements services
# user/services/rpc/service.py

class UserService(user_pb2_grpc.UserServiceServicer):
    def GetUsers(self, request, context):
        return UserResource(request, context, pb2.UserResponse).get_users()

def serve():
    port = 5000
    server = grpc.server(
        futures.ThreadPoolExecutor(max_workers=50),
        interceptors=[ProcessInterceptor()],
    )
    user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
    server.add_insecure_port(f'[::]:{port}')
    server.start()
    logger.info("Service started on port: %s (env: %s)", port, ENV)
    server.wait_for_termination()
Copy the code

Get_users is a standard generic action, so you don’t need to implement it in the Resource. You can define the Model and Schema and use it directly.

Start the RPC service:

python main.py --rpc
Copy the code
  1. GRPC implements services

To invoke the user service from the ORDER service, simply use the Bali add {service} command to create all the files required by clients in the ORDER project.

bali add user
Copy the code

Call it in code

from clients import UserClient
users = UserClient().get_users()
Copy the code

Project summary

GRPC is really the direction of the future, simple and cross-lingual. The cloud native Service mesh supports gRPC by default, such as Linkerd. Gateway products in microservice scenarios also support gRPC by default.

The question of where to put the business code will become a topic in all projects. The Bali framework has only two layers, Model and Resource, so developing microservices will be more efficient without much thinking. A set of code compatible with both HTTP and RPC services.