Book Image

Building Python Microservices with FastAPI

By : Sherwin John C. Tragura
3 (2)
Book Image

Building Python Microservices with FastAPI

3 (2)
By: Sherwin John C. Tragura

Overview of this book

FastAPI is an Asynchronous Server Gateway Interface (ASGI)-based framework that can help build modern, manageable, and fast microservices. Because of its asynchronous core platform, this ASGI-based framework provides the best option when it comes to performance, reliability, and scalability over the WSGI-based Django and Flask. When working with Python, Flask, and Django microservices, you’ll be able to put your knowledge to work with this practical guide to building seamlessly manageable and fast microservices. You’ll begin by understanding the background of FastAPI and learning how to install, configure, and use FastAPI to decompose business units. You’ll explore a unique and asynchronous REST API framework that can provide a better option when it comes to building microservices. After that, this book will guide you on how to apply and translate microservices design patterns in building various microservices applications and RESTful APIs using the FastAPI framework. By the end of this microservices book, you’ll be able to understand, build, deploy, test, and experiment with microservices and their components using the FastAPI framework.
Table of Contents (17 chapters)
1
Part 1: Application-Related Architectural Concepts for FastAPI microservice development
6
Part 2: Data-Centric and Communication-Focused Microservices Concerns and Issues
11
Part 3: Infrastructure-Related Issues, Numerical and Symbolic Computations, and Testing Microservices

Designing and implementing REST APIs

The Representation State Transfer (REST) API makes up the rules, processes, and tools that allow interaction among microservices. These are method services that are identified and executed through their endpoint URLs. Nowadays, focusing on API methods before building a whole application is one of the most popular and effective microservices design strategies. This approach, called an API-first microservices development, focuses first on the client’s needs and then later identifies what API service methods we need to implement for these client requirements.

In our online academic discussion forum app, software functionality such as user sign-up, login, profile management, message posting, and managing post replies are some of the crucial needs we prioritized. In a FastAPI framework, these features are implemented as services using functions that are defined using Python’s def keyword, with the association of the appropriate HTTP request method through the path operations provided by @app.

The login service, which requires username and password request parameters from the user, is implemented as a GET API method:

@app.get("/ch01/login/")
def login(username: str, password: str):
    if valid_users.get(username) == None:
        return {"message": "user does not exist"}
    else:
        user = valid_users.get(username)
        if checkpw(password.encode(), 
                   user.passphrase.encode()):
            return user
        else:
            return {"message": "invalid user"}

This login service uses bcrypt’s checkpw() function to check whether the password of the user is valid. Conversely, the sign-up service, which also requires user credentials from the client in the form of request parameters, is created as a POST API method:

@app.post("/ch01/login/signup")
def signup(uname: str, passwd: str):
    if (uname == None and passwd == None):
        return {"message": "invalid user"}
    elif not valid_users.get(uname) == None:
        return {"message": "user exists"}
    else:
        user = User(username=uname, password=passwd)
        pending_users[uname] = user
        return user

Among the profile management services, the following update_profile() service serves as a PUT API service, which requires the user to use an entirely new model object for profile information replacement and the client’s username to serve as the key:

@app.put("/ch01/account/profile/update/{username}")
def update_profile(username: str, id: UUID, 
                     new_profile: UserProfile):
    if valid_users.get(username) == None:
        return {"message": "user does not exist"}
    else:
        user = valid_users.get(username)
        if user.id == id:
            valid_profiles[username] = new_profile
            return {"message": "successfully updated"}
        else:
            return {"message": "user does not exist"}

Not all services that carry out updates are PUT API methods, such as the following update_profile_name() service, which only requires the user to submit a new first name, last name, and middle initial for partial replacement of a client’s profile. This HTTP request, which is handier and more lightweight than a full-blown PUT method, only requires a PATCH action:

@app.patch("/ch01/account/profile/update/names/{username}")
def update_profile_names(username: str, id: UUID, 
                          new_names: Dict[str, str]):
    if valid_users.get(username) == None:
        return {"message": "user does not exist"}
    elif new_names == None:
        return {"message": "new names are required"}
    else:
        user = valid_users.get(username)
        if user.id == id:
            profile = valid_profiles[username]
            profile.firstname = new_names['fname']
            profile.lastname = new_names['lname']
            profile.middle_initial = new_names['mi']
            valid_profiles[username] = profile
            return {"message": "successfully updated"}
        else:
            return {"message": "user does not exist"}

The last essential HTTP services that we included before building the application are the DELETE API methods. We use these services to delete records or information given a unique identification, such as username and a hashed id. An example is the following delete_post_discussion() service that allows a user to delete a posted discussion when given a username and the UUID (Universally Unique Identifier) of the posted message:

@app.delete("/ch01/discussion/posts/remove/{username}")
def delete_discussion(username: str, id: UUID):
    if valid_users.get(username) == None:
        return {"message": "user does not exist"}
    elif discussion_posts.get(id) == None:
        return {"message": "post does not exist"}
    else:
        del discussion_posts[id] 
        return {"message": "main post deleted"}

All path operations require a unique endpoint URL in the str format. A good practice is to start all URLs with the same top-level base path, such as /ch01, and then differ when reaching their respective subdirectories. After running the uvicorn server, we can check and validate whether all our URLs are valid and running by accessing the documentation URL, http://localhost:8000/docs. This path will show us a OpenAPI dashboard, as shown in Figure 1.2, listing all the API methods created for the application. Discussions on the OpenAPI will be covered in Chapter 9, Utilizing Other Advanced Features.

Figure 1.2 – A Swagger OpenAPI dashboard

Figure 1.2 – A Swagger OpenAPI dashboard

After creating the endpoint services, let us scrutinize how FastAPI manages its incoming request body and the outgoing response.