Production-ready microservice in Rust: 1. Setup the workspace
Posted 2023-05-07 14:00:00 by sanyi ‐ 3 min read
Setup a multi-package workspace for our application
For complex Rust applications I prefer to use a workspace with multiple packages. This way we can split our application into smaller, loosely coupled parts but we don't have to maintain a different repository for each of them and we can work with the whole codebase in one IDE window.
To start our new project, let's create a directory to hold the workspace:
$ mkdir shelter-project
$ cd shelter-project
and create a Cargo.toml
file there:
[workspace]
members = [
"shelter_main"
]
Also initialize a git repository, to prevent cargo new
from creating a new
repository for every package:
$ git init
I use JetBrains CLion with the Rust plugin so I usually add a .gitignore
file to ignore the .idea
folder and the Rust target
folder:
.idea
target
Let's create the shelter_main
package:
$ cargo new shelter_main
Now our directory structure looks like this:
.git
.gitignore
Cargo.toml
shelter_main/
Cargo.toml
src/
main.rs
We can run cargo build
in the main workspace directory, the build creates
an executable in target/debug/shelter_main
and a single Cargo.lock
file
in the main workspace directory.
Now we can commit our code:
$ git add .gitignore Cargo.lock Cargo.toml shelter_main/
$ git commit -m 'workspace setup'
I usually design my Rust application to handle multiple CLI commands.
For example, a shelter_main serve
command to run the REST application
server, a shelter_main migrate
to run the database migrations, etc.
For command-line argument processing I usually use the clap
crate,
we can add it to the shelter_main
package:
$ cargo add clap@4
Let's modify shelter_main/src/main.rs
:
use clap::{Arg, Command};
pub fn main() {
let command = Command::new("Dog Shelter sample application")
.version("1.0")
.author("Sandor Apati <[email protected]>")
.about("A sample application to experiment with Rust-based microservices")
.arg(
Arg::new("config")
.short('c')
.long("config")
.help("Configuration file location")
.default_value("config.json"),
);
let _matches = command.get_matches();
}
This creates a simple parameter named -c
or --config
to set
the configuration file location for our application.
We can test is easily:
$ cargo build
$ ./target/debug/shelter_main --help
A sample application to experiment with Rust-based microservices
Usage: shelter_main [OPTIONS]
Options:
-c, --config <config> Configuration file location [default: config.json]
-h, --help Print help
-V, --version Print version
A few more crates I usually add to the project:
$ cargo add [email protected]
$ cargo add anyhow@1
The dotenv
crate supports the loading of environment variables from .env
files.
The anyhow
crate simplifies error handling in Rust - it will be important later.
Modify our main.rs
to use them:
use clap::{Arg, Command};
use dotenv::dotenv;
pub fn main() -> anyhow::Result<()> {
dotenv().ok();
let command = Command::new("Dog Shelter sample application")
.version("1.0")
.author("Sandor Apati <[email protected]>")
.about("A sample application to experiment with Rust-based microservices")
.arg(
Arg::new("config")
.short('c')
.long("config")
.help("Configuration file location")
.default_value("config.json"),
);
let _matches = command.get_matches();
Ok(())
}
The next post will be about the implementation of the CLI sub-commands.
You can find the sample code on GitHub