/blueprint-hexagonal-rust

Hexagonal architecture example in Rust

Primary LanguageRustMIT LicenseMIT

Blueprint hexagonal architecture in Rust

👋 This project goal is to provide a blueprint of an implementation of hexagonal architecture in Rust.
⚠️ This project is in a work in progress mode and will be updated when I will find the time (and the courage) to work on it

This project is a tiny task scheduler, you can create some tasks that will be run and get their status.

Hexagonal architecture

For hexagonal architecture presentation please refer to Alistair Cockburn presentation

This project:
Domain & Infra code are split in two projects.
I know it is not perfect and it could be improved (and it will be) but there is all the basics of hexagonal architecture from my point of view :

  • Separation of domain logic and infrastructure (side-effects) code
  • Portable domain
  • Testable domain
  • Composition in infra code to execute as wanted
  • Each secondary.adapter have a proper model for its purpose

Domain

domain schema

Ports :

  • TaskSchedulePort (executor::ports::primary::TaskSchedulerPort) : Contract to schedule some Task and get their status
  • TaskStoragePort (executor::ports::secondary::TaskStoragePort) : Contract to store tasks and their executions
  • TaskExecutionPort (executor::ports::secondary::TaskExecutionPort) : Contract for task execution
  • IdGeneratorPort (executor::ports::secondary::IdGeneratorPort) : Contract to generate ids for tasks

Infra

infra schema

Adapters :

  • CLI Input (primary::cli::CliOpt) : Input of the application via command line
  • UUID IdGenerator (secondary::adapter::id_generator::UUIDGeneratorAdapter) : Ig generator based on UUID
  • Local ExecutionAdapter (secondary::adapter::execution::LocalExecutionAdapter) : Task execution secondary.adapter on local machine
  • Database StorageAdapter (secondary::adapter::storage::database::SqliteStorageAdapter) : Database storage
  • InMemory StorageAdapter (secondary::adapter::storage::memory::InMemoryStorageAdapter) : InMemory storage

Composability

Storage can be in memory using secondary::adapter::storage::memory::InMemoryStorageAdapter or with sqlitedb using secondary::adapter::storage::database::SqliteStorageAdapter. This behavior is configured by the new_storage_adapter in secondary::adapter::storage module.
Currently, the choice is hard coded but it could be configurable.

Setup

Database init (Optional)

Application roll automatically database migration but you can manualy do the needed migrations.

To initialize database please install cargo install diesel_cli for sqlite.

Run the migrations at root of the project: diesel migration run --database-url <database_path>

Build

Use cargo for build : cargo build

Configuration

The configuration of the application is loaded from settings.toml file.

You can override configuration by env var (example export DATABASE_URL=override.db override database.url settings)

Execute

Run from cargo : cargo run -- <args>

Command line execution : ./target/debug/blueprint-hexagonal-infra <args>

Usage

Run a task :

blueprint-hexagonal-infra-run 0.1.0

USAGE:
    blueprint-hexagonal-infra run [FLAGS] [OPTIONS] <command>...

FLAGS:
    -w, --wait       Wait the execution of the task and print status

OPTIONS:
    -n, --name <name>    Name of the task for later querying

ARGS:
    <command>...    Command to be executed by the task

Example : ./target/debug/blueprint-hexagonal-infra run ls /

Status of a task :

USAGE:
    blueprint-hexagonal-infra status id <id>
    blueprint-hexagonal-infra status name <name>

Example : ./target/debug/blueprint-hexagonal-infra status id f340a3d3-f5ca-42b1-9a3b-312112836cd8

Database connection

 sqlite3 test.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> select * from tasks;
1477bb1b-73b2-468e-a4e2-fc571423da25||ls /home||SUCCESS|fteychene
linuxbrew

TODO

  • Improve genericity for the domain using Into and From (limitation on secondary ports see #limitations)
  • Add unit tests
  • Add real life secondary.adapter
  • CLI Adapter for input
  • Improve error management
  • Improve documentation
  • Run migration through code for database
  • Add input validation
  • Split task execution
  • Split code to client and server
  • Make connection through unix socket from cli to server

Limitations

Into for secondary ports

Concerned code part

Goal : Main goal was to provide a way for adapters to not adapt their internal domains to application domain as function result.
To provide this feature we would like to provide functions of secondary ports to return types with only Into<DomainStruct> constraint.

Issue :

error[E0038]: the trait `std::convert::Into` cannot be made into an object
  --> infra/src/main.rs:37:1
   |
37 | fn test() -> Box<dyn Into<u8>> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::Into` cannot be made into an object
   |
   = note: the trait cannot require that `Self : Sized`

error: aborting due to previous error

The trait Into cannot be used as part of a dynamic type due to error E0038