Building a Microservice with Rust
Rust is a great language, currently was the language that I most study in 2019 and in 2020(so far). Rust has interoperability with any language pretty much. Rust is also great for containers and to run in Kubernetes. Today I want to show how to build a simple microservice. We will use Actix, Tokio-Postgress, and other libraries. We will use Postgress as our source of truth and we will run it in docker(for development sake). We also will use Barrel + some customs migration structure I created. The code will be all async and non-blocking IO. I hope you have fun, let's get started.
Architecture
Here we have a multi-layer architecture where the business rules and rest definitions are defined on the news-contract. The SOA contract is defined at news-contract + news-service, the Structure(News) is defined at news-contract. The rest endpoint definitions + service is defined on news-service. The postgress persistence is defined at news-dao.
Code Structure
We have 5 projects, there one global workspace which is one project, the top-level one. Them we have news-contract: which is the SOA contract part, we have the News Structure used on the code.
news-dao: We have the reactive persistence code using tokio-postgres and all the CRUD operations for the News resource.
news-migrations: We are using barrel + custom logic to create tables and add data for the sake of testing.
news-service: Here we have the endpoint, service implementation and the main class with the actix-web framework being configured.
Each project has its own dependencies and thats defined by the Cargo.toml file.
We also have 2 scripts that deal with Docker containers. One to run Postgres and the other to run psql.
Migrations
Now Let's take a look at how we can do migrations(Create tables and add records into Postgres SQL).
We are connecting on the Postgres Database running on docker and creating a Vector containing all migrations and we are running this migration. Then we are doing a for all migration going through them one by one and doing a matched checking if they went well or not.
Now let's look at the next rust code, what is a migration.
I created a structure called NewsMigration where there is a new(function used to create the struct) and the method to run the migration called (run). As you see I created a second struct called CreateTableNewsMigration and a trait implementation using(impl) inside the code I'm using the barrel and create a table structure, the barrel will generate the Postgres SQL INSERT syntax. Finally, we are running the generated script in Postress using the pg_client. You might see code here a bit odd: &news_table[..] where we are passing the reference of the String news_table and passing a slice([..]) copying the string and passing through the method.
SOA Contract
Let's take a look into one part of the Soa Contract, the Struct News.
We are defining a Struct called News and I'm using serde and serde_json in order to be able to serialize and deserialize this struct. I'm also implementing a trait called Display in order to be able to print the struct. Finally, there is a unit test ad the end of the file and I'm testing that I can print the struct.
Endpoints / Services
Here I define the HttpServer actix and I'm defining several handlers like: index, list_news, insert_news,get_news_by_id, and delete_news_by_id. The server will be running on the localhost port 8080. All information is logged using log and env_logger creates.
Now let's take a look at the endpoint.rs file where we have the rest definitions.
Here we have macros using REST operations like PUT, DELETE and GET. Each function handler is public and basically, here I'm calling the service and getting the result back from the service and serializing into a json result.
This is the service implementation, we dont have any REST or actix dependencies here. Here is the right place to implement validations, business logic, and delegate to the dao crate. All functions on the CRUD are async.
Dao
Here is where the magic happens, we have the tokio-postgress code. Let's take a look.
This is the DAO implementation. There is a method to connect on a postgress database called connect() which happens in an async and non-blocking way. There I'm showing how to do a find_by_id. Using the tokio postgress client I'm executing a query passing the SELECT query doing a WHERE by ID. In Postgress ID is handle as a UUDI so I need to convert to string thats why you see id::text=$1 and in the same line I'm passing the ID I got from parameter with &[&id]. The DAO has more functions which you can check out on the complete code in mine GitHub.
Video: Code Walkthrough + Dead Demo
Let's takes a look at the video I made with a code walkthrough and a dead demo.
Rust Microservice from Diego Pacheco on Vimeo.
Full Source Code: https://github.com/diegopacheco/rust-playground/tree/master/rust-microservice
cheers,
Diego Pacheco
Architecture
Here we have a multi-layer architecture where the business rules and rest definitions are defined on the news-contract. The SOA contract is defined at news-contract + news-service, the Structure(News) is defined at news-contract. The rest endpoint definitions + service is defined on news-service. The postgress persistence is defined at news-dao.
Code Structure
We have 5 projects, there one global workspace which is one project, the top-level one. Them we have news-contract: which is the SOA contract part, we have the News Structure used on the code.
news-dao: We have the reactive persistence code using tokio-postgres and all the CRUD operations for the News resource.
news-migrations: We are using barrel + custom logic to create tables and add data for the sake of testing.
news-service: Here we have the endpoint, service implementation and the main class with the actix-web framework being configured.
Each project has its own dependencies and thats defined by the Cargo.toml file.
We also have 2 scripts that deal with Docker containers. One to run Postgres and the other to run psql.
Migrations
Now Let's take a look at how we can do migrations(Create tables and add records into Postgres SQL).
We are connecting on the Postgres Database running on docker and creating a Vector containing all migrations and we are running this migration. Then we are doing a for all migration going through them one by one and doing a matched checking if they went well or not.
Now let's look at the next rust code, what is a migration.
I created a structure called NewsMigration where there is a new(function used to create the struct) and the method to run the migration called (run). As you see I created a second struct called CreateTableNewsMigration and a trait implementation using(impl) inside the code I'm using the barrel and create a table structure, the barrel will generate the Postgres SQL INSERT syntax. Finally, we are running the generated script in Postress using the pg_client. You might see code here a bit odd: &news_table[..] where we are passing the reference of the String news_table and passing a slice([..]) copying the string and passing through the method.
SOA Contract
Let's take a look into one part of the Soa Contract, the Struct News.
We are defining a Struct called News and I'm using serde and serde_json in order to be able to serialize and deserialize this struct. I'm also implementing a trait called Display in order to be able to print the struct. Finally, there is a unit test ad the end of the file and I'm testing that I can print the struct.
Endpoints / Services
Here I define the HttpServer actix and I'm defining several handlers like: index, list_news, insert_news,get_news_by_id, and delete_news_by_id. The server will be running on the localhost port 8080. All information is logged using log and env_logger creates.
Now let's take a look at the endpoint.rs file where we have the rest definitions.
Here we have macros using REST operations like PUT, DELETE and GET. Each function handler is public and basically, here I'm calling the service and getting the result back from the service and serializing into a json result.
This is the service implementation, we dont have any REST or actix dependencies here. Here is the right place to implement validations, business logic, and delegate to the dao crate. All functions on the CRUD are async.
Dao
Here is where the magic happens, we have the tokio-postgress code. Let's take a look.
This is the DAO implementation. There is a method to connect on a postgress database called connect() which happens in an async and non-blocking way. There I'm showing how to do a find_by_id. Using the tokio postgress client I'm executing a query passing the SELECT query doing a WHERE by ID. In Postgress ID is handle as a UUDI so I need to convert to string thats why you see id::text=$1 and in the same line I'm passing the ID I got from parameter with &[&id]. The DAO has more functions which you can check out on the complete code in mine GitHub.
Video: Code Walkthrough + Dead Demo
Let's takes a look at the video I made with a code walkthrough and a dead demo.
Rust Microservice from Diego Pacheco on Vimeo.
Full Source Code: https://github.com/diegopacheco/rust-playground/tree/master/rust-microservice
cheers,
Diego Pacheco