The source content for blog.juliobiason.me
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

145 lines
4.3 KiB

+++
title = "Command Pattern Experiments in Rust"
date = 2021-07-22
[taxonomies]
tags = ["design patterns", "command", "rust"]
+++
3 years ago
I've been doing some experiments in using the command pattern in Rust and found
at least two ways to write it.
<!-- more -->
3 years ago
## But first... Why?
3 years ago
There is one thing I'm trying to do in which the command pattern fits
perfectly: I want to have a library with all the actions and then plug
interfaces on top of, being either a CLI interface or a Web interface or
whatever interface. For that, the logic behind the action should be somewhat
isolated from whatever source it is calling it.
## What It Is
The Command Pattern is described as having one object for each action ('cause,
you know, the patterns focused more on OO designs) and each of those have an
`execute` method which... well... execute the command.
## The Enum Solution
3 years ago
As what you have is a list of actions, one of the ideas was to use `Enums`,
even if it is not exactly what the pattern describes.
3 years ago
Say, we have two actions can be called: deposit money and withdraw money.
Simple.
3 years ago
So one could have the following Enum[^1]:
```rust
enum Command {
Deposit(Decimal),
Withdraw(Decimal),
}
```
Because Rust allows enum variants to carry a value with them, the amount to be
deposited/withdraw is attached directly to the variant.
And then you have the `execute()` function. And, again, 'cause Rust allows
3 years ago
adding functions to almost everything, what I did was add a method in the Enum:
```rust
impl Command {
fn execute(&self) -> Result<...> {
match self {
Deposit(value) => do_the_deposit(value),
Withdraw(value) => withdraw_money(value),
}
}
}
```
... and so on.
3 years ago
To use it, I put something pretty close to this in my interface layer:
```rust
let value = incoming_external_request.value()
let command = match incoming_external_request.command() {
"deposit" => Command::Deposit(value),
"withdraw" => Command::Withdraw(value),
}
command.execute();
```
It feels fine and all, but it tends to make a mess with the amount of content
3 years ago
that goes in or around the `impl`, in my opinion. But, at the same time, the
dispatch layer (between the service/enum layer and the interface layer) is
pretty basic.
One solution to the amount of "content in or around `impl`" could be use
multiple `impl`: So I could have a module `deposit.rs` which `impl`s the
`do_the_deposit` and another module `withdraw.rs` which also `impl`s inside the
enum with the `withdraw_money` content. But I'd still need the center `execute`
to do the proper "dispatch" of the calls.
## The Trait Solution
3 years ago
The trait solution is very close to what the pattern is: You create a trait
(interface) and "impl" it for all the commands, which are just structs. For
example:
```rust
trait Command {
fn execute(&self) -> Result<...>;
}
```
```rust
struct Deposit(Decimal);
impl Command for Deposit {
fn execute(&self) -> Result<...> {
// what was `do_the_deposit` now goes here.
}
}
struct Withdraw(Decimal);
impl Command for Withdraw {
fn execute(&self) -> Result<...> {
// what was `withdraw_money` now goes here.
}
}
```
3 years ago
... which feels a bit cleaner, since all related things to Deposit or Withdraw
are now tied together.
However, this causes a slight problem with the interface layer: Now it can't
just return one sized thing: It needs to return a dynamic dispatchable content,
like `Box<dyn Command>`, which isn't as pretty as the direct Enum/Struct/sized
content.
On the other hand, since `Box` implements `Deref`, once the interface throws
something-that-implements-Command, one could just call `execute()` directly on
it.
```rust
let command = interface_that_returns_a_box_dyn_command();
command.execute();
```
## Where I see those two
I can see the Enum being used for simple, single domain architectures. Since
all things are related, they can reside correctly under the Enum.
But when dealing with multiple domains, the trait/dynamic dispatch feels more
at home: Related things get into their own module, in their own space and the
3 years ago
idea of mixing them (for example, if you have a money domain and a tag domain,
and you want to tag money operations) goes on layer above.
---
[^1]: `Decimal` is not part of Rust Standard Library, but can be used from the
[rust_decimal crate](https://crates.io/crates/rust_decimal).