diff --git a/content/code/command-pattern-rust.md b/content/code/command-pattern-rust.md index bfb110e..a0c3ae2 100644 --- a/content/code/command-pattern-rust.md +++ b/content/code/command-pattern-rust.md @@ -6,27 +6,34 @@ date = 2021-07-22 tags = ["design patterns", "command", "rust"] +++ -I've been playing a bit with the command pattern in Rust and found at least two -ways to write it. +I've been doing some experiments in using the command pattern in Rust and found +at least two ways to write it. -## But first... why? +## But first... Why? -There is one thing I'm trying to do with all this: 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. -And the command interface fits perfectly in this. +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 -Because you have a list of actions, one of the ideas was to use `Enums`. +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. -Say, we have two actions the user can trigger: deposit money and withdraw -money. Simple. +Say, we have two actions can be called: deposit money and withdraw money. +Simple. -So one could have the following Enum: +So one could have the following Enum[^1]: ```rust enum Command { @@ -39,7 +46,7 @@ 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 -adding functions to almost everything, what I did was: +adding functions to almost everything, what I did was add a method in the Enum: ```rust impl Command { @@ -54,9 +61,21 @@ impl Command { ... and so on. +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 -that goes in or around the `impl`, in my opinion. But, at the same time, means -that our interface layer must just return `Command` and that's all fine. +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 @@ -66,8 +85,9 @@ to do the proper "dispatch" of the calls. ## The Trait Solution -The trait solution is very close to what the pattern is: You create a trait and -"impl" it for all the commands. For example: +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 { @@ -91,10 +111,13 @@ impl Command for Withdraw { } ``` -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`, which isn't as pretty as the direct Enum/Struct/sized -content from the Enum. +... 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`, 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 @@ -105,9 +128,6 @@ let command = interface_that_returns_a_box_dyn_command(); command.execute(); ``` -Also, because all commands are structures, they can hold the same information -as Enum variants would. - ## Where I see those two I can see the Enum being used for simple, single domain architectures. Since @@ -115,4 +135,10 @@ 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 -idea of mixing them goes on layer above. +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). diff --git a/content/code/command-pattern-rust.pt.md b/content/code/command-pattern-rust.pt.md new file mode 100644 index 0000000..a0b2224 --- /dev/null +++ b/content/code/command-pattern-rust.pt.md @@ -0,0 +1,149 @@ ++++ +title = "Experimentos com Command Pattern em Rust" +date = 2021-07-22 + +[taxonomies] +tags = ["design patterns", "command", "rust"] ++++ + +Eu tenho feito alguns experimentos implementando o command pattern em Rust e +encontrei pelo menos duas formas de implementar. + + + +## Mas Primeiro... Por que? + +Existe uma coisa que eu estou tentando fazer em que o command pattern se +encaixa perfeitamente: Eu quero ter uma biblioteca com todas as ações do +sistema e implementar uma interface em cima disso, sendo que pode ser uma CLI +ou uma interface web ou uma interface qualquer. Para isso, a lógica por trás da +ação deve estar de alguma forma isolada da origem da chamada. + +## O Que É + +O command pattern é descrito como ter um objeto para cada ação (porque, +basicamente, os patterns são mais focados em projetos orientados a objetos) e +cada um destes tem um método chamado `execute` que... bem... executa o comando. + +## A Solução Enum + +Como o que você têm é uma lista de ações, uma das ideias foi usar `Enum`, mesmo +que isso não seja exatamente o que pattern descreve. + +Digamos que nós temos duas ações que podem ser chamadas: Depositar dinheiro e +sacar dinheiro. Simples. + +Assim, podemos ter o seguinte Enum[^1]: + +```rust +enum Command { + Depositar(Decimal), + Sacar(Decimal), +} +``` + +Como Rust permite que as variantes de um Enum carreguem um valor com elas, o +valor a ser depositado ou sacado fica anexado junto com a variante. + +E então você tem a função `execute()`. E, de novo, porque Rust permite que +sejam adicionadas funções em basicamente tudo, o que eu fiz foi adicionar um +método diretamente no Enum: + +```rust +impl Command { + fn execute(&self) -> Result<...> { + match self { + Depositar(valor) => faz_o_deposito(valor), + Sacar(valor) => sacar_dinheiro(valor), + } + } +} +``` + +E assim por diante. + +Para usar, eu coloquei algo parecido com isso na minha camada de interface: + +```rust +let valor = requisicao_externa.valor(); +let comando = match requisicao_externa.comando() { + "depositar" => Command::Depositar(valor), + "sacar" => Command::Sacar(valor), +} +comando.execute(); +``` + +Tudo fica simples e tal, mas existe uma tendência a deixar uma bagunça com a +quantidade de conteúdo que fica dentro ou ao redor do `impl`, na minha opinião. +Mas, ao mesmo tempo, a camada de dispatch (que fica entre a camada de +serviço/enum e a camada de interface) é bem básica. + +Uma solução para para a quantidade de "conteúdo dentro ou ao redor do `impl`" +seria o uso de múltiplos `impl`: Ter um módulo `deposito.rs` que faz o `impl` +de `faz_o_deposito` e outro módulo `saque.rs` que também faz o `impl` dentro do +enum com o conteúdo de `sacar_dinheiro`. Mas eu ainda precisaria centrar todas +as operações no `execute` para ter um dispatch correto. + +## A Solução com Traits + +A solução com trait é bem parecida com o que o pattern diz: Você cria uma trait +(interface) e "impl" em todos os comandos, que são structs. Por exemplo: + +```rust +trait Command { + fn execute(&self) -> Result<...>; +} +``` + +```rust +struct Depositar(Decimal); +impl Command for Depositar { + fn execute(&self) -> Result <...> { + // o que era o `faz_o_deposito` vai aqui. + } +} + +struct Sacar(Decimal); +impl Command for Sacar { + fn execute(&self) -> Result <...> { + // o que era o `sacar_dinheiro` vai aqui. + } +} +``` + +... o que parece um pouco mais limpo, já que todas as coisas relacionadas com +Deposito ou Saque estão juntas agora. + +Entretanto, isso causa um pequeno problema com a camada de interface: Agora ela +não pode mais retorna algo com tamanho fixo: É necessário usar um conteúdo com +dispatch dinâmico, como `Box`, o que não é tão direto quando um +Enum/Struct/conteúdo com tamanho. + +Por outro lado, como `Box` implementa `Deref`, uma vez que a interface retorne +algo-que-implementa-Command, basta chamada `execute()` diretamente nele. + +```rust +let comando = interface_que_retorna_um_comando_num_box_dyn(); +comando.execute(); +``` + +## Onde Eu Vejo Esses Dois + +Eu consigo ver o uso do Enum em arquiteturas simples, com apenas um domínio. +Como toas as coisas são relacionadas, elas podem viver tranquilamente dentro do +Enum. + +Mas quando estamos lidando com múltiplos domínios, a solução de trait/dispatch +dinâmico parece fazer mais sentido: Coisas relacionadas nos seus próprios +módulos, nos seus próprios espaços e a ideia de misturar os mesmos (por +exemplo, se você tiver um domínio de tags e um domínio de dinheiro, e quer +colocar tags nas operações de dinheiro) ficaria na camada acima deles. + +--- + +[^1]: `Decimal` não faz parte da biblioteca padrão do Rust, mas pode ser usada + a partir da [crate rust_decimal](https://crates.io/crates/rust_decimal). + +