👋 This project goal is to provide a blueprint of an implementation of hexagonal architecture in Rust.
This project is a tiny task scheduler, you can create some tasks that will be run and get their status.
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
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
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
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.
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>
Use cargo for build : cargo build
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)
Run from cargo
: cargo run -- <args>
Command line execution : ./target/debug/blueprint-hexagonal-infra <args>
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
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
- Improve genericity for the domain using
Into
andFrom
(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
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