Implementing the state pattern in Rust

Some months ago I was reading this book about Rust called, “Rust for rustaceans”, “Idiomatic programming for Experienced Developers”. The warning “for experienced developers” is very well deserved. I felt very inexperienced, Rust is not easy. But overall it’s a great book. It has the best explanations for asynchronous programming, concurrency and parallelism for instance.
Among all the interesting things, one that caught my eye was what seemed to be the best implementation of the state pattern. Ever. In any language.
What does, from my point of view, make some code better than other? I don’t think I’m alone in here - and I’ve probably stated it quite often in all my blog posts- and it’s basically the code that’s easy to understand and that can’t be used in an unintended way. There’s no better way to explain better the intention, than to make the code impossible to use in any other way that’s not the intended.
Making it idiot proof, where the idiot can be very well me in the future - that wants to minimize the time needed to understand the code - therefore it should be easy to get it, simple - and wants to extend it right away - respecting the “O” of SOLID (Open close principle, the code should be Open to extension, close to modification) - which is generally achievable by using known patterns like the state pattern.
So there’s this section of the book that starts like this:
The type system is an excellent tool to ensure that your interfaces are obvious, self-documenting, and misuse-resistant. You have several techniques at your disposal that can make your interface very hard to misuse, and thus, make it more likely that they will be used correctly.
I couldn’t agree more that these: obvious, self-documenting and misuse-resistant are goals to aspire to. Especially in the domain layer when coding the business rules and designing the solution . Actually, the first thing I try to think about in a code review is “how can I misuse it? can I break the business rules with this code?” and the harder it is, the better the code. (Maybe this can explain to you my natural hate for setters). Spending some effort in this is what will give you a rich domain.
Going back to the book, it gives this example about a rocket that can only be moved when it’s been Launched and not when it’s still in the ground.
struct Grounded;
struct Launched;
struct Rokect<Stage = Grounded> {
stage: std::marker::PhantomData<Stage>, // this PhantomData is a super advanced rust stuff
// that only super advanced rust coders like me understand to say
// that it's not really part of the struct. (yeah, I don't really quite get it).
}
impl Rocket<Grounded> {
pub fn launch(self)-> Rocket<Launched>{}
}
impl Rocket<Launched> {
pub fn accelerate(&mut self){}
pub fn deaccelerate(&mut self){}
}
Listing 3-2 using marker types to restrict implementations (I am not putting the full listing in case I get into copyright issues… if you want to see it all, just get the book! I paid for it and it was worth it! - from now on the code and so on will be mine).
This really really great. The code that might allow you to misuse doesn’t even exist! It’s impossible for an idiot like me to accelerate a Rocket that’s not Launched! We are ensuring business rules (if Rockets are your business) by programming only what’s possible. We are not opening any door for any misuse.
And we’re relying on the type system for that! So when I try to code the misuse, it won’t even compile (and if using IDE, I’ll see it right away I’m doing something wrong). The bug detection can’t be earlier in the development cycle. (A reason for why I will always prefer typed compiled languages before interpreted languages - the amount of money you save with this, enabling developers to catch bug so early- covers by far the money needed for your developers to learn and master a new language if they don’t already).
Going further with this example, if we consider Grounded and Launched as states, we have here a state pattern implementation. Now, this is in an idyllic situation, where we are only coding the domain - a phase that should go first when designing solutions, coding the domain, with showing how the domain is being executed with some tests as example code-.
In a more practical system that we’d like to use in production, we will need to persist things as well as building from there, or from other infrastructure places like HTTP requests. We are missing the infrastructure and application layers for a complete system, Here’s where things get a little bit muddy.
It’s not really straightforward to reach this pure domain respecting the type system - even harder if you’re not a master of Rust… But that’s what we will do here in this blog post, trying to sacrifice as little as possible this purity in the domain while having a practical application.
What about refactoring Gurus example for the state pattern in Rust?
I like that example as it’s very straightforward, and easy to understand, but compared to the one from the book I see three main disadvantages:
it’s very easy to misuse: I can call any state pattern functions from anywhere as well as the main object function without respecting it’s state.
It also forces you to implement scenarios that simply won’t happen (the state is defined in a trait with the functions
pub trait State { fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State>; fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State>; }so that means that all the states have to implement those, but on the
playingState, the functionplaywill do nothing. On the books solution there’s no need to even write this code!It doesn’t use the type system. See the interface it accepts a
Box<self>? it’s becausethe concrete state is unknown at compile time.Being honest, #1 can be avoided with proper visibility rules on the domain, having public just the states and private the
audio playeror having a facade. I had to do the same in my solution after all.Being still honest for #2, I had to do something similar in my solution when trying to add the infra and app layers to reach the domain.
And complete honesty, #3 might as well be an advantage. It gives you flexibility, and the implementation if the outer layers, application/infrastructure, will definitely be easier this way! You will avoid some contortions with the type system indeed.
But I could manage to have the full system with hexagonal architecture working with such a pretty inner domain. And the main reason… I realized that I could check that example in refactoring Gurus after I already managed to solve all of the problems and made all the hard decisions… But well, that’s what is actually worth sharing, and the reason of this blog post, a serendipitous outcome after all.
So here goes nothing.
The domain
I will go with the typical example of a blog post, with little number of states - but we can always make up more for the sake of the example - which are just a draft state and a published state. The attributes of the blog post are also small (and so I don’t need to modify too much the code from what I actually did)
This is the main entity, the blog post. Since post alone can be ambiguous without context, I’ve decided to name it BlogPost so it’s concise.
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct BlogPost<State> {
id: Id,
content: Content,
_state: std::marker::PhantomData<State>
}
I spend longer than necessary thinking about visibility, I don’t want to open any option for the misuse of my code, that’s why the contents inside are not public, only the type BlogPost is public - necessary, as it’s one piece that the outer layers will need to instantiate, it’s the door to our domain API. More on that later, I can advance though that I will decide to make those public despite my efforts not to.
The attributes of the BlogPost for the sake of brevity, are just two. We are always about having a rich domain, so of course the attributes aren’t simple primitives but value objects. I believe I’ve explained somewhere else the best implementation - from my point of view, of course- for value objects in either Go or Rust - it has a lot to do with visibility, and avoiding a misuse of the code- sounds familiar?
I’m adding those default macros #[derive(Debug, PartialEq, Copy, Clone)] that are very convenient and harmless - spread those through the value objects inside too. We can get picky, for example, that we shouldn’t clone the BlogPost as each instance has its own id, so it can exist only one, but it’s not worth it to go that pure in those discussions - clone might be very useful for an internal function that owns (in “code” meaning the rust concept of ownership )the BlogPost and returns something else, like any functional based modification that do “copy on write”. (instead of a mutable reference on the struct’s method pub fn modify(&mut self) you pass the actual thingy pub fn modify(self). That’s supposed to be safer and to introduce less bugs - this is the functional way, immutable, you “generate” a new thing. In OO the mut self would be the way to go. Honestly, as we say in Spanish, tanto monta, monta tanto, doesn’t matter, and the OO way is also safe (it has to do with the design of the object that might open inconsistent modifications from different paths).
Let’s get out of that rabbit hole and put the rest of the code, the methods of BlogPost that need to be there independently from the state the BlogPost is in. That in general will be just getters - but don’t just add them because entities needs getters, add them because something needs them - in that case, as those are things that modify/change the object, they are in teh query side, the something that needs them can be just a test. (In general, do not open the code for a test, the tests should be an example on how you want your code to be used, so if you change the code for the test, the tests looses its value - but getters are something inoffensive).
impl <State: BlogPostState> BlogPost<BlogPostState> {
pub fn id(&self) -> &Id {
&self.id
}
pub fn content(&self) -> &Content {
&self.content
}
pub fn state(&self) -> &'static str {
<State as BlogPostState>::NAME
}
}
Ok, it’s not necessary, but I actually made the State to implement a trait called BlogPostState, this is completely unnecessary but it’s a nice to have. That’s the trait:
pub trait BlogPostState: Sized {
const NAME: &'static str;
}
It just forces the state to have a constant called NAME.
Ok, let’s implement those states, first the draft. It will have the constructor function, indicating that only a Draft BlogPost can be created, it will be impossible to create a BlogPost in a different state.
use super::*;
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Draft;
impl BlogPostState for Draft {
const NAME: &'static str = "Draft";
}
impl BlogPost<Draft>{
pub fn new(id: Id, content: Content) -> Self {
BlogPost {
id,
content,
_state: std::marker::PhantomData,
}
}
about
use super::*, the alternative should be other ways to refer to the same thing likeuse crate::blog_post::domain. I’m being adamant about visibility, and so domain constructs should only be able to see thing on the same domain layer, or we would break the inward dependency of a clean architecture. Refuse to use the “import” feature from IDE’s as they will mess up. Sticking to that will point out problems in visibility that you might miss otherwise.
If you are copying and pasting the code, you might notice that this won’t compile. Despite my efforts for making the attributes private, if this is in a different file, we can’t access the attributes. There’s only two options, using the same file, risking a very long file, or making them public.
Using the same file will not only make it very long, but it will affect my obsessive compulsive disorder on which I won’t be to organize each state in each file and see it in a glance what each state has implemented inside. Also, if I see a file per state, chances are that if there is a new state, I will recognize this pattern (not the state pattern per se, but the pattern of creating a file per state), and so less chances to mess things up, than scrolling in a very big file - where I might implement the same state twice! (I can see myself saying “how odd, the name “Draft” is being used already, let’s call this state then “DraftState”, and then having those two states repeated… yeah, that’s definitely something that might happen to me).
Ideally, in the perfect language, I’d be able to tell what things belong to the same module, despite being in different files, but that’s something not possible nor in Rust or Go…
Ok, so let’s go back and let’s make the attributes public:
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct BlogPost<State> {
pub(crate) id: Id,
pub(crate) content: Content,
pub(crate)_state: std::marker::PhantomData<State>
}
Let’s restrict, at least the pub to just the crate. Not that will save from my idiot future myself doing things like this in the same crate:
let id = Id::new(Uuid::new_v4());
let number = Content::new(123);
let blog_post = BlogPost{
id,
number,
_state: PhantomData,
};
but it will prevent from external users of the crate misusing this. Later, we will see another reason for why we need that public - again despite my efforts. So we just need to pray and hope that my idiot self in the future will see the autocomplete from the IDE with the “new” function and decide to use that instead of bypassing it.
Let’s do for a second the Publish state, that will just have nothing, for brevity, and then let’s implement the publish function.
use super::*;
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Published;
impl BlogPostState for Published {
const NAME: &'static str = "Published";
}
impl BlogPost<Published>{
}
impl BlogPost<Draft>{
//[.. the new here]
#[must_use]
pub fn publish(self) -> (BlogPost<Published>, BlogPostPublishedDomainEvent) {
let published_domain_event = BlogPostPublishedDomainEvent{blog_post: self.clone()};
let published_blog_post = BlogPost::<Published> {
id: self.id,
content: self.content,
_state: std::marker::PhantomData,
};
(published_blog_post, published_domain_event)
}
}
The beauty of it is that this function is only available from Draft, so only from Draft you can publish something. Then, I like the functional way (+ the own concept of Rust), so this method “eats” a BlogPost. Finally, I like that domain functions that execute business logic, like publishing something, generate Domain Events and I add the #[must_use] to make sure that when this function is called, the caller does something with that domain event!
I was hesitant about the return of this function - returning two things, it’s a practice that seems more natural in Go than in Rust, normally with the error. The alternative is to use the mutable reference, so we don’t return the object, we just modify it, and there’s only one thing to return, the domain event. I have no strong opinion either way. I don’t think one way represents the intention and the business rules better than the other - else I’d have a strong opinion… - I go the owning way just because it feels more functional and keeps the aggregate root immutable.
The Domain Event doesn’t have anything special, it’s just a mechanical implementation, as the struct is small, the aggregate root is small, I just copy everything there. A record for the prosperity. Something that happened. If you’re curious that’s the implementation.
use super::*;
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct CasePublishedDomainEvent{
pub(crate) blog_post: BlogPost<Draft>
}
In that one, I make everything public with no hesitation. Domain Events are supposed to be immutable. I hope that the name of this struct is clear enough to identify that this is a domain event. (Yes, for some stuff I’m less strict, but hey, after all, I had to give it in and make other stuff I didn’t wanted public before!).
Later we will add too the Domain Event for Draft Created - I just wanted to focus on other things- you can imagine the implementation is just symmetric to this one.
Let’s add the test for the Draft state, to say that this is where we construct it and how we publish it!
#[cfg(test)]
mod tests {
use uuid::Uuid;
use crate::blog_post::domain::*;
use crate::stubs::blog_post_stub_draft;
#[test]
fn test_blog_post() {
let id = Id::new(Uuid::new_v4());
let content = Content::new("initial content".to_string());
let blog_post = BlogPost::new(id.clone(), content.clone()); // it knows it's blog_post: BlogPost<Draft>
assert_eq!(blog_post.state(), Draft::NAME);
let (blog_post, de) = blog_post.publish();
assert_eq!(blog_post.id(), &id);
assert_eq!(blog_post.content(), &content);
assert_eq!(blog_post.state(), Published::NAME);
}
}
Here I show how I expect the code to be used. You use the new constructor to create the aggregate root, which must give you the <Draft> state, and from there you can publish it.
I’m a little bit heartbroken with the tests as they are “white box” tests, that is, the test module actually lives inside the module, if it’s in the same file - which is standard practice in Rust. White box tests should be avoided, as they give you different access to the pieces you are designing, and you can completely misuse them. Even in unit tests. A unit test, like in the example, should show how the other layers - application, infra- or other parts of the domain should use this module, and therefore should only be allowed the interface that the module offers - that’s why we spend time thinking about visibility. This is just the encapsulation principle. Only in a little number of scenarios a white box test might be useful: when it prevents you to make a function public just for testing purposes.
Now, in Rust, to force a black box test, the test must live outside, where you’d normally place integration tests that test fully some stuff - and that will be quite confusing, especially for other Rust programmers. So I’ll keep the tests in the same file, and pretend that I only have access to the module I’m testing the way the module intends to be used.
Tracing Bullet
Until now, the example is pretty much the same as in the book, or as in gurus, I’m not giving much extra value in this blog post so far. But the point was to go the full way and see what problems arise. We have our idyllic rich domain, but we need a way to execute it, we need to build the rest of the application, until we can have something that can be deployed to production. Or well, that at least works. For production I’d focus on other aspects -like proper logging or monitoring- that are not relevant for a blog post titled “implementing the state pattern in Rust”.
But let’s go all the way, with a persistence mechanism and a web server that opens the endpoints to execute our minimal domain. This effort is called “tracing bullet” programming from the “pragmatic programmer” book. A tracing bullets shows the path for the full system. We don’t need all the features that we are going to offer. We don’t need to implement all the business rules, only a small subset, in this case, it’s going to be just the “create draft” operation. But dealing already with the persistence mechanism and the web server now, will set the base and the guide to implement the rest of the features.
Let’s begin outlining how to execute the create draft business logic. We will have a command that looks like this:
#[derive(Debug, Deserialize)]
pub struct CreateDraftCommand {
pub id: Uuid,
pub content: String,
}
Commands are just DTOs and so I don’t hesitate to make their contents public. Like Domain Events, I expect this to be immutable too. Maybe I’m too used with Go that you assume things are immutable unless you pass a pointer, but Rust has also a very immutable nature, you need to express mut on the stuff you want to edit, or by default everything is immutable. So I don’t give it much thought.
I also use the Uuid package - here I’m being lazy - it’s just a good trade off. We should use primitives, and maybe if I wanted to be strict I should do. Right now I don’t care that much if when trying to construct this Command with something that’s not uuid breaks. That scenario should return a bad request, I’ll keep that in mind when dealing with user input (http request body) and trying to construct that. An alternative, passing the primitive, should eventually break when trying to create the value object - in that case, the ID, a very simple one that just encapsulates the uuid or a String primitive… I can write another blog post about the importance of this practice that seems just “YAGNI” right now. Either case, the user was trying to send invalid data and the response, at the web server level, should be a 400 bad request.
Now that we have the command, we can outline the command handler:
pub struct CreateDraftHandler<R: BlogPostRepository> {
blog_post_repository: R,
}
impl<R: BlogPostRepository> CreateDraftHandler<R> {
pub fn new(blog_post_repository: R) -> Self {
Self { blog_post_repository }
}
pub async fn handle(
&self,
command: CreateDraftCommand,
) -> Result<(), BlogPostError> {
// construct the value objects, if there's the chance for Error, add the "?".
let blog_id = Id::new(command.id);
let content = Content::new(command.content);
// execute the business logic. That looks exactly as the test above, as it should.
let blog_post = BlogPost::new(blog_id, content);
// save the result.
self.blog_post_repository
.add(blog_post).await
}
}
I am going a little bit object oriented way with this CommandHandler alternatively it can be just a function and you pass the repository as parameter - that might be more “rusty”, it’s defintely more functional. I don’t think it should change too much the instantiation of those pieces - let’s call them “services” kind of pieces- that are the code (those include command handlers, repositories and controllers).
This code is quite straightforward. The application layer where the “xx”(command/event/query) handlers are usually placed, they just use the pieces and execute stuff. In this case, I’m explicitely saving the aggregate root - that could be achieved in other ways, but decorating all the command handlers into a transactional behaviour. With proper aggregate root design, a transaction should affect only one aggregate root and the xx handlers would normally reflect that, but just doing operations to a single aggregate root (there are exceptions, - domain services for example- but the “transactionality” of the xx handlers is normally maintained even then - if you modify two aggregate roots in a single action, you’d normally count that a single transaction, if one fails, you’d want to roll back the other one, you are falling into the land of “orchestrators” and “sagas” - normally thought to be at full microservice level but it can also happen into a single service / single transaction level. You should run away from it and/or think twice the design and/or challenge whatever business rule that forces you into this land.
Last and definitely not least I am using a trait bound BlogPostRepository over a generic type R. We definitely need something that implements that trait bound, but it doesn’t need to be a generic type, instead you can use a trait object Box<dyn BlogPostRepository>:
pub struct CreateDraftHandler {
blog_post_repository: Box<dyn BlogPostRepository>,
}
the choice is yours, but I recommend the trait object - yeah I did the other way, because I had to experience by myself how the alternative would be, and the examples will be with the generic type-.
With a generic type, you will need to add the generic type notation to everything that uses this trait, you will get tired of those kind of notation impl<R: BlogPostRepository> CreateDraftHandler<R> { that will bubble up to the entrypoint - namely the controller.
It also forces you to decide which implementation of the trait bound you are going to use before compiling. I don’t think that’s a big drawback, as the interface is there mainly to separate business logic concerns from persistence, and the implementation is usually just one, the one that you’re going to use in production. If you are testing and passing a different implementation, you will pass that other implementation, no big deal.
But this becomes awkward if you want to decide in run time different implementation - impossible with a generic - or maybe use an environment variable to decide to use one implementation over another. You can have a function with this signature: fn generate_repository() -> Box<dyn BlogPostRepository but you cannot have a function that returns a generic type (there’s a trick to return an enum, that has inside each variant the implementation - you can return that, but you have to implement it).
If the above is not going to be an issue, you can use just a generic type. In my case, I don’t mind deciding beforehand at compile time the implementation. But you can see why I recommend the trait object solution and not the generic option.
Actix
Let’s finish the tracing bullet programming with the parts that are straightforward. I’m going to use actix-web crate as the web server, and sqlx for persistence. I am just going to wire the parts, and later we will continue the discussion.
That would be the main:
use std::net::TcpListener;
use actix_web::{web, App, HttpServer};
use actix_web::middleware::Logger;
use blog_post_service::{db_connection, health_check, wire_blog_post_module};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db_pool = db_connection().await;
let db_pool_actix_data = web::Data::new(db_pool.clone());
let listener = TcpListener::bind("0.0.0.0:8000")?;
env_logger::init();
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.route("/health_check", web::get().to(health_check))
.app_data(db_pool_actix_data.clone())
.configure(|cfg| wire_blog_post_module(cfg, db_pool_actix_data.clone()))
})
.listen(listener)?
.run()
.await
}
I just want something working fast, so I will deal later with encapsulating some part, so I can test the whole application in my integration tests (something explained very well in the book Zero To Production by Luca Palmieri).
My lib.rs is like this:
mod actix;
pub use actix::*;
mod configuration;
pub use configuration::*;
pub mod blog_post;
pub use blog_post::*;
Next to main and lib I have my actix.rs with just the health check:
use actix_web::{HttpRequest, HttpResponse};
pub async fn health_check(_req: HttpRequest) -> HttpResponse {
HttpResponse::NoContent().finish()
}
And then I have my blog_post module with my layers in it. This is the tree:
src/
├── actix.rs
├── blog_post
│ ├── actix_wiring.rs
│ ├── application
│ │ ├── create_draft_handler.rs
│ │ ├── mod.rs
│ │ └── publish_handler.rs
│ ├── domain
│ │ ├── mod.rs
| | ├── ....
│ ├── infrastructure
│ │ ├── create_draft_controller.rs
│ │ ├── mod.rs
│ │ └── persistence
│ │ ├── blog_post_pgsql_mapping.rs
│ │ ├── blog_post_pgsql_repository.rs
│ │ └── mod.rs
│ └── mod.rs
├── configuration.rs
├── lib.rs
└── main.rs
The last files that you need to make it all work are the configuration, that just starts the db_connection:
use std::str::FromStr;
use sqlx::PgPool;
use sqlx::postgres::{PgConnectOptions, PgPoolOptions};
const DATABASE_URL: &str = "DATABASE_URL";
pub async fn db_connection() -> PgPool {
let base_url = std::env::var(DATABASE_URL).expect("Missing environment variable DATABASE_URL");
let options = PgConnectOptions::from_str(base_url.as_str())
.expect("Invalid base URL");
println!("options {:?}", options);
match PgPoolOptions::new()
.max_connections(5)
.acquire_timeout(std::time::Duration::from_secs(5))
.connect_with(options)
.await
{
Ok(pool) => pool,
Err(e) => {
eprintln!("Failed to connect to Postgres: {:?}", e);
std::process::exit(1);
}
}
}
Ah, and this is my cargo.toml
[package]
name = "blog-post-service"
version = "0.1.0"
edition = "2024"
// Removing some stuff that it's not important for this blog post.
[dependencies]
uuid = { version = "1.1.2", features = ["v4", "serde"] }
serde = { version = "1.0.219", features = ["derive"] }
tokio = { version = "1.44.2", features = ["rt", "rt-multi-thread", "macros"] }
async-trait = "0.1"
actix-web = "4.10.2"
env_logger = "0.11.8"
[dependencies.sqlx]
version = "0.8.3"
default-features = false
features = [
"macros",
"postgres",
"uuid",
"chrono",
"migrate",
"json",
"runtime-tokio"
]
We have all the scaffolding, let’s “wire” the blog post module, first take a look at the controller:
pub async fn create_draft_controller<R: BlogPostRepository>(
request: web::Json<CreateDraftCommand>,
handler: web::Data<CreateDraftHandler<R>>,
) -> Result<HttpResponse, BlogPostError> {
handler.handle(request.into_inner()).await?;
Ok(HttpResponse::Created().finish())
}
So I need to bubble up the generic, as I said it will happen. This just executes the handler and that’s it.
We need some conversion from my domain/application types (the error and the commands) to the actix types. For the command just adding the serde derive is enough - and not that harmful I believe. I would be more careful on my domain stuff, as one might start modifying it just for the sake of serializing it, which is a different concern than applying business rules! I’d suggest to move serializing / de-serializing / marshalling /unmarshalling / array-ing / json conversion out of the domain! Instead, add types that encapsulate those concerns that can be constructed from the domain blocks, but out of the domain, in the application layer, keeping the domain clean.
For the BlogPostError we need this, done in the same controller file:
impl Display for BlogPostError {
// this could be implemented in the same domain, but the same way I don't like adding
// serializing concerns in the domain, I don't like adding display concerns in the domain
// but I would give in if you insisted that this is more comfortable to have it in the domain.
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
BlogPostError::Default => write!(f, "A default error occurred"),
_ => { write!(f, "A default error occurred")}
}
}
}
impl ResponseError for BlogPostError {
fn error_response(&self) -> HttpResponse {
match self {
BlogPostError::Persistence(_str) => {
HttpResponse::InternalServerError().finish()
}
BlogPostError::Default => {
HttpResponse::BadRequest().json("Something went wrong.")
}
_ => { HttpResponse::BadRequest().json("Something went wrong.")}
}
}
}
Finally, let’s see this actix_wiring.rs that add this controller to a route, and instantiates the command handler the repository and the controller:
pub fn wire_blog_post_module(cfg: &mut web::ServiceConfig, db_pool_data: web::Data<PgPool>) {
let blog_post_repository = BlogPostPgsqlRepository::new(db_pool_data.get_ref().clone());
// handlers
cfg.app_data(web::Data::new(CreateDraftHandler::new(blog_post_repository)));
// routes
cfg.route("/blog-post", web::post().to(create_blog_post_draft_controller::<BlogPostPgsqlRepository>));
}
You should have everything ready to be able to start the application in local cargo run --bin community-service; I haven’t shown yet the repository and persistence parts that we’ll see right away, so without that part probably it won’t compile, just be a little bit more patient.
Database
To make it work fully in local you will need an actual database. The fastest is to have a docker container with that. As I’ve said I’m going to use PstgreSQL, so that’s the docker compose:
services:
# we can have also a container with our rust app here. I recommend this
# if you plan tom deploy to production in a cloud and the artifact deployed is a docker
# container. But for now, to go fast, I will assume you have rust installed and the rust app
# will be executed on your laptop.
blog-post-service-db:
image: postgres:15.4-alpine
container_name: blog-service-db
restart: on-failure
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=blog-db
ports:
- '5432:5432'
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
You will need an .env file (if you’ve been paying attention to all of the code I copy pasted, you saw that the app expects some env variable to configure properly the DB, that’s teh .env file with just the DB string:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/blog-service-db
And to start the app you will need to export this file. Execute it like this:
# first start the db container:
$docker compose up community-service-db &;
# and execute the app with the env file
$set -a; source .env; cargo run --bin blog-post-service;
Later when we have all the pieces in place, you should be able to curl to your localhost and have a proper response:
curl -X POST http://localhost:8000/blog-post \
-H "Content-Type: application/json" \
-d '{"id": "650e8400-e29b-41d4-a716-446655440000", "content": "content"}'
All right “trace bullet” programming done. There’s one missing peace for it all to work but a part from that you can have a full app working end to end in your local. Invaluable step when building a new service, focus on having something that works - and then keep adding “verticals” end to end, reviewing the parts that might have been done in a hurry. As long as you keep the separation of concerns constant, without breaking clean architecture dependency rules, there will be no overhead by going fast on those infrastructure/application layers that are necessary to have something working. This is mainly a mechanical work that just extends your rich domain to the “real” infrastructure world, and if the domain is good - as we’re very much trying here with pretty beautiful adequate and most precise implementations of the business rules, you have nothing to worry about.
But the deal of this blog post is about the state pattern, not the whole full app (which is part of the deal, but not the focus). That’s why I went over it like a bullet without commenting too much all of that part. There’s always things to discuss at every step. But now it’s not the time. Let’s continue with the subject at hand.
The repository
The missing part is the PgSQL implementation. So let’s go back and focus to our pretty domain, the command handler that we outlined and what should be the repository interface. What would be that interface? This is where things get a little bit interesting. My first attempt was to have something like this:
#[async_trait]
pub trait BlogPostRepository {
async fn find<State>(&self, id: Id) -> Result<Option<BlogPost<State>>, BlogPostError>;
async fn add<State>(&self, blog_post: BlogPost<State>) -> Result<(), BlogPostError>;
}
A repository pattern doesn’t need to many functions (instead of having a thousand personalized query functions, use the criteria pattern to abstract those). It represents a collection of items, so you can find one or add one. We might need to add “save” later, but I like when somehow this is hidden with some handler decorators dealing with the transactions.
The async_trait, just use it or you end up having function signatures that look like Egyptian hieroglyphs. Repository functions are by definition infrastructure operation - from a functional point of view, an implementation of a repository will never be pure (well, when using a real database and not an in memory array…) - that’s a good rule of thumb when deciding if something belongs or not to the domain, or needs an interface. For that reason, just get prepared and make those functions return a Result as those operations can always fail. For the find the Ok variant will return an option, whether the BlogPost was found, with it in it, or not found, with None then (the best representations of the intention of the result of this function).
But let’s focus on the problems: I’m declaring this generic type <State> . If you went for the trait object solution above, with Box<dyn BlogPostRepository> that will not compile when instantiating the handler:
let blog_post_repository = BlogPostPgsqlRepository::new(db_pool_data.get_ref().clone()); //yes, assume
// the implementations is the PgSQL. See it later.
let handler = CreateDraftHandler::new(Box::new(blog_post_repository));
That will give you a compile error: where BlogPostRepository is not dyn compatible the compiler says it, for the trait to be dyn compatible it needs to allow building a vtable. Let’s improve that by adding the trait bound, as we already have it, this might allow helping building a vtable as we just implemented that trait in a couple of structs.
#[async_trait]
pub trait BlogPostRepository {
async fn find<State: BlogPostState>(&self, id: Id) -> Result<Option<BlogPost<State>>, BlogPostError>;
async fn add<State: BlogPostState>(&self, blog_post: BlogPost<State>) -> Result<(), BlogPostError>;
}
Still no luck. All right, I am still using the generic type, instead of the object trait so I can move forward ignoring this, but first smell that this interface might bring problems. For now let’s continue. Now instantiating the repository like this:
let blog_post_repository = BlogPostPgsqlRepository::new(db_pool_data.get_ref().clone()); //yes, assume
// the implementations is the PgSQL. See it later.
let handler = CreateDraftHandler::new(blog_post_repository);
And continue implementing with the actual database queries. I am going to use PostgreSQL and the sqlx crate. Assume this migration happened (another blog post for that).
-- migrate:up
CREATE TABLE blog_posts
(
id uuid,
content text,
state TEXT NOT NULL DEFAULT 'Draft',
PRIMARY KEY (id)
);
On one side we have a very beautiful representation of our domain with the best way to represent states, on the other side, we still need to persist it. The important thing is that we designed the domain independently from persistence concerns. But back to the real world where you depend on databases, we need something to mark the state of our aggregate root! The most straightforward way is to just have a column indicating such thing. Don’t sweat it.
Where you have to sweat is in translating from your domain to what you actually persist in the database. But you’ll do good to keep those two things a part. The most important reason is that you don’t want to model your pretty domain having to think how things are being persisted, you need to be free from this concern. The second reason is that those are actually two different things (as proven with the very example I’m working on here). And the third is that it’s not that complicated to have some translation between the two.
You could rely on some ORM, but I find them more and more intrusive (so far the only advantage I’ll admit is when they handle this “transactionaliy” for you so you don’t need to declare the “save” in your repository interfaces - you can achieve that with PHP’s Doctrine for example. But a part from that, the work that they supposedly save you, is work I’d rather do myself so I control exactly how what’s persisted is converted to my domain. (in the places where I use Doctrine, I end up adding so many customized maps that really begs the question if it’s worth it). So I will rely on other tools instead.
So here we will have a struct that represents what’s persisted. That’s in the file infrastructure/persistence/log_post_pgsql_mapping.rs that you might wonder what was that about:
use sqlx::FromRow;
#[derive(Debug, FromRow)]
pub struct BlogPostRow {
pub id: Uuid,
pub content: String,
pub state: String,
}
Note the FromRow derive, that’s from the sqlx package and a comfortable way to map your queries to a struct.
Now we need a way to transform that to our domain and vice versa. From our domain to the database can be straightforward:
impl<State: BlogPostState> From<BlogPost<State>> for BlogPostRow {
fn from(value: BlogPost<State>) -> Self {
BlogPostRow {
id: value.id().value().clone(),
content: value.number().value(),
state: value.state().to_string(),
}
}
}
The other way around is more interesting. Since we’re building from the database that can be dirty instead of using the from trait we should use the tryfrom. A straightforward implementation can look like that:
impl<State: BlogPostState> TryFrom<BlogPostRow> for BlogPost<State> {
type Error = BlogPostError;
fn try_from(value: BlogPostRow) -> Result<Self, Self::Error> {
let id = Id::new(value.id);
let content = Content::new(value.content);
match value.state.as_str() {
Draft::NAME => Ok(BlogPost {
id,
content,
_state: PhantomData,
}),
Published::NAME => Ok(BlogPost {
id,
content,
_state: PhantomData,
}),
other => Err(BlogPostError::Persistence(format!("Unknown blogPost state: {}", other))),
}
}
}
Maybe you can see a problem here -it wont’ compile this way- but before let’s focus about constructing domain in this layer. We can’t use the actual constructor, that takes into account the business rules, and will generate a domain event, we have to actually bypass those rules here. And I hate it. The only way to not bypass the rules, is to make the whole aggregate root an event sourced aggregate root. I have other blog posts explaining in detail what’s that and how to do it. Summing up here, it means persisting the domain events and applying those - with the business rules - from a “default” state to the final state. You can very rightly consider this over engineering - but it’s the purest way to respect your reach domain. But for now, we’ll need to break our domain so we can build an aggregate root from the persistence mechanism.
“Good thing” that we made the attributes public, so we can construct the struct this way. I just have this uneasy feeling, that one can set whatever inconsistent data for between the attributes. So in the aim to hope for my future self to see it, I’ll create a new constructor, that’s not the domain one - because for domain, when creating this, the domain event “blog post created” must happen! but well, some other constructor, that I hope I will see it and use it instead. And in case there’s some invariant I have to maintain even when fetching from the database, I will code it.
Other languages and ORM might solve that with some kind of Reflection, kind of meta programming adding the values to the attributes. It’s another option for bypassing the domain rules. I prefer to be explicit though and deliberate, even when doing things of which I don’t like to do.
Now where to place that constructor, your choice, I’d place it in that very same file, you can do it:
impl<State> BlogPost<State> {
pub(crate) fn from_primitives(id: Id, content: Content) -> Self {
Case { id, number, _state: std::marker::PhantomData }
}
}
but you can place this in the domain too - it’s about maintaining some possible business invariants of your aggregate root right? this might return a Result in the case they might be broken. On the other hand, adding code in our domain just because we are in crossroads with our persistence choices seems also not good. So you choose the least evil.
Having that, let’s amend a little bit the trait TryFrom:
fn try_from(value: BlogPostRow) -> Result<Self, Self::Error> {
let id = Id::new(value.id);
let content = Content::new(value.content);
match value.state.as_str() {
Draft::NAME => Ok(BlogPost::<Draft>::from_primitives(
id,
content
)),
Published::NAME => Ok(BlogPost::<Published>::from_primitives(
id,
content
)),
other => Err(BlogPostError::Persistence(format!(
"Unknown blog post state from DB: {}",
other
))),
}
}
Ok, now let’s focus on why this doesn’t compile. Our trait implementation impl<State: BlogPostState> TryFrom<BlogPostRow> for BlogPost<State> { is for the type BlogPost<State> but that function is actually returning a specific type, Draft or Published!
That’s quite a big problem. Possible alternatives that come to my mind. Maybe let’s forget about tryFrom and have a function that it’s not bounded by this generics? Well the function will actually need to be bounded by the generics the same as try_from. So then I need to implement a try_from for each state? (impl<DraftState> TryFrom<BlogPostRow> for BlogPost<DraftState> and impl<PublishedState> TryFrom<BlogPostRow> for BlogPost<PublishedState> … )
Well, that won’t work. Imagine our naive “find” implementation:
#[async_trait]
impl BlogPostRepository for BlogPostPgsqlRepository {
async fn find<State>(&self, id: Id) -> Result<Option<BlogPost<State>>, BlogPostError> {
let row = sqlx::query_as::<_, BlogPostow>("SELECT * FROM blog_posts WHERE id = $1")
.bind(id.value())
.fetch_optional(&self.pool)
.await
.map_err(|e| BlogPostError::Persistence(e.to_string()))?;
// Convert Option<BlogPostow> -> Option<BlogPostContext>
match row {
Some(r) => Ok(Some(BlogPost::try_from(r)?)),
None => Ok(None),
}
}
It’s not that we can’t use the try_from in the match, is that the find interface itself will not work:
async fn find<State: BlogPostState>(&self, id: Id) -> Result<Option<BlogPost<State>>, BlogPostError>;
If we have an implementation per state, we need to return that specific state, not the generic. So that forces us to change the repository interface!! We would need something like this:
#[async_trait::async_trait]
pub trait BlogPostRepository {
async fn find_draft(&self, id: Id) -> Result<Option<BlogPost<DraftState>>, BlogPostError>;
async fn find_published(&self, id: Id) -> Result<Option<BlogPost<PublishedState>>, BlogPostError>;
// you can extend with other methods too:
async fn save_draft(&self, blog_post: BlogPost<DraftState>) -> Result<(), BlogPostError>;
async fn save_published(&self, blog_post: BlogPost<PublishedState>) -> Result<(), BlogPostError>;
}
That’s, well, an option. A valid option actually, but I see some problems. For example, let’s focus on the publish command handler:
#[derive(Debug, Deserialize)]
pub struct PublishCommand {
pub id: Uuid,
}
pub struct PublishCommandHandler<R: BlogPostRepository> {
blog_post_repository: R,
}
impl<R: BlogPostRepository> PublishCommandHandler<R> {
pub fn new(blog_post_repository: R) -> Self {
Self { blog_post_repository }
}
pub async fn handle(
&self,
command: PublishCommand,
) -> Result<(), BlogPostError> {
let blog_post_id = Id::new(command.id);
let blog_post = self.blog_post_repository.find_draft(blog_post_id).await?.unwrap();
let (published, _de) = blog_post.publish();
self.blog_post_repository
.add_published(published).await
}
}
To publish a blog post, we need a draft, right? so we use the find_draft function. That seems actually quite convenient at first right? But, what happens if it’s not found? It can be for two different options: the whole BlogPost does not exist, or BlogPost exists but it’s on a state that’s not Draft. How can we distinguish between the two? finding it again per state? another call to the database then? Or implement a “generic” one?… we are here precisely because we couldn’t implement the generic function!
You might be happy with returning an “BlogPost not found” error to the user despite it being in a different state, and call it a day. It’s a working approach after all. But I feel uneasy. At some point somebody will ask why is it not found? And I will say to frontend: “hey whenever you receive a “not found” when publishing just change the error message!” and then well, it’s a slowly descent to chaos here.
So this option doesn’t seem to be go anywhere.
Context to the rescue
There’s another option, to use a wrapper enum, to hide the state, something like this:
pub enum AnyBlogPost {
Draft(BlogPost<Draft>),
Published(BlogPost<Published>),
}
That’s a more feasible solution. I wanted to avoid it an enum with all the states because it’s very close to the solution to just have an enum inside the “state” attribute - you feel very tempted to go like this. But that’s not implementing the state pattern... We would have “ifs” everywhere for each domain function checking the state and executing one logic or another, what the state pattern precisely avoids.
Also this AnyBlogPost seems a little bit out of place. It’s weird to have that concept of AnyBlogPost, that appeared mainly because we couldn’t manage to satisfy the repository trait, but in our domain there’s no such thing as “any blog post” it’s either draft or published. Isn’t there something more accurate?
Well maybe not from the domain side, but from other implementations of the State Pattern in OO languages, there’s this facade that encapsulates and hides the aggregate root behind, generally called Context. It has both the state implementation and the actual object. One is supposed to call that facade methods and internally the proper business rules will happen.
And by a happy accident it matches the wrapper enum solution. Or, in other words, we can implement the context with this wrapper enum . We can maintain the pretty states achieving that awesomeness where the scenarios that can’t happen aren’t even coded. And we can get a little bit more control, encapsulated in the domain, on those “wrong” scenarios, like publishing a blog post that’s not draft.
Let’s see how I’d look like. Going back to our domain, we don’t touch the blog post but we add this wrapper enum, that we’ll call it context
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum BlogPostContext {
Draft(BlogPost<Draft>),
Published(BlogPost<Published>),
}
We should behind this context all the operation. And ideally, other parts of our own project should only have access to this context. You can tell I’ve been trying, to not allow open doors for the code to be misused. But like before, either all the code is in a single file, or that becomes impossible, we’ll resign to show the intention with the re exports in our mod.rs, but that won’t prevent access to whatever we have set as pub - and we had to, remember, to construct from the database or for other reasons.
Anyway, since this is supposed to be facade that the other things use, let’s at least do it well this very first time, and hopefully next times I will see how I did it and repeat the intention. Let’s implement some methods in the context:
impl BlogPostContext{
pub fn id(&self) -> &Id {
match self {
BlogPostContext::Draft(c) => c.id(),
BlogPostContext::Published(c) => c.id(),
}
}
pub fn content(&self) -> &Content {
match self {
BlogPostContext::Draft(c) => c.content(),
BlogPostContext::Published(c) => c.content(),
}
}
pub fn state(&self) -> &'static str {
match self {
BlogPostContext::Draft(c) => c.state(),
BlogPostContext::Published(c) => c.state(),
}
}
}
It’s a little bit verbose. But to avoid this repetition, please don’t do a trait. A Blog Post is not a trait, you can’t implement a random object to behave like a blog post - well of course you can, it’s just not the case here. Making a trait will just add unnecessary complexity. So if we can avoid it, if it doesn’t represents properly the business rule, better avoid this extra complexity, I’m already at the edge of complexity with this implementation for the state pattern. (A trait for this would be a clear example of YAGNI, a flexibility mechanism totally unnecessary that just complicates discussions, and quite probably a bad abstraction. DRY is the most dangerous principle that if not done well, will lead you to overly coupled and complicated solution, don’t fall for this trap)
To avoid this repetition you can develop some macro of that kind that is converted to code. That would be preferably.
How would the constructor be with it, if we go back to the BlogPost Constructor:
impl BlogPost<Draft>{
pub fn new(id: Id, content: Content) -> (Self, BlogPostDraftCreatedDomainEvent) {
let blog_post = BlogPost {
id,
content,
_state: std::marker::PhantomData,
};
let domain_event = BlogPostDraftCreatedDomainEvent{blog_post};
(blog_post, domain_event)
}
We could - if it was the same file - to not make that new public, ah I can keep daydreaming, but well, I like order, so in the other file in the domain where I’m implementing the BlogPostContext I will have this:
impl BlogPostContext{
pub fn new(id: Id, content: Content) -> (Self, BlogPostCreateDomainEvent) {
let (blog_post, de) = BlogPost::new(id, content);
(BlogPostContext::Draft(blog_post), de)
}
The signature of the method should be very similar. I know of a book that would complain that having this middle class that basically just calls the class inside doesn’t really help. I agree, but this blog post is proof that I’ve tried any other way - while keeping the core of the state implementation this way - before going for this. And if we made the object inside completely private - which I can’t as I’ve been saying - nobody would know that underneath we’re basically just transmitting the call.
On the other hand - I honestly wouldn’t mind allowing calling directly a new for a draft and bypassing this. We need to this for the repository interface, but we might as well have this handler that uses the state draft directly:
impl<R: BlogPostRepository> CreateDraftHandler<R> {
pub async fn handle(
&self,
command: CreateDraftCommand,
) -> Result<(), BlogPostError> {
let id = Id::new(command.id);
let content = Content::new(command.content);
let (blog_post, de) = BlogPost::new(id, content);
self.blog_post_repository
.add(blog_post.into()).await
}
}
We would need to have some function that converts that to context, but that can be straightforward with a from trait:
impl From<BlogPost<Draft>> for BlogPostContext{
fn from(value: BlogPost<Draft>) -> Self {
BlogPostContext::Draft(value)
}
}
This is because, the gist of it all, is to finally have a repository interface that can be implemented!
#[async_trait]
pub trait BlogPostRepository {
async fn find(&self, id: Id) -> Result<Option<BlogPostContext>, BlogPostError>;
async fn add(&self, blog_post: BlogPostContext) -> Result<(), BlogPostError>;
}
The repository doesn’t deal anymore with the specific BlogPost<State> but with the context. We will save and retrieve the Context. Maybe I should have started with this first of all. But I had to investigate each possibility and see what were the advantages and disadvantages of each case.
Dealing with the context, and removing the generic <State> from the repository interface makes the repository interface usable to be referenced also dynamically at runtime. So we can use trait Objects (something like repository: Box<dyn BlogPostRepository> will compile. Again, trait objects are the way to go, more than generics. So another problem solved with this.
Let’s take a loot how the publish operation would be from the Context too:
impl BlogPostContext{
pub fn publish(&self) -> Result<(BlogPost<Published>, BlogPostPublishedDomainEvent), BlogPostError> {
match self {
&BlogPostContext::Draft(ref draft) => {Ok(draft.publish())},
_ => Err(BlogPostError::CantDoThisActionFromThisState) // hopefully you'll spend more time than me coding the errors.
}
}
With that wrapper enum the code here will be similar, we will do a match and execute the code that exists for the state that implement that operation.
On the other solutions that match would have been at the application layer - with some solutions adding the complication to find the BlogPost several times, once per existing state! This way, this logic is encapsulated inside the domain, which I like better (but well, not a super big deal). I ended up doing something of which I criticized from the Refactoring Gurus solution, that is, coding each possible combination.
But the difference from there, is that if I create a new state, I will be force to implement all the functions even if it makes sense or not. In here, I still code only what needs to be coded. I only need to an arm in my match from the Context method, and most likely not even that as probably the default option _ => Err(xxx) will be what I’ll need. I think this is excuse enough to say that this way is prettier.
For the sake of completion, let’s take a look at the publish handler.
impl<R: BlogPostRepository> PublishCommandHandler<R> {
pub fn new(blog_post_repository: R) -> Self {
Self { blog_post_repository }
}
pub async fn handle(
&self,
command: PublishCommand,
) -> Result<(), BlogPostError> {
let id = Id::new(command.id);
let blog_post_context = self.blog_post_repository.find(id).await?.unwrap();
let (published, de) = blog_post_context.publish()?;
self.blog_post_repository
.add(published.into()).await
}
}
Well, ok I keep returning the entity inside instead of the context in the domain functions - just realized that- that’s why I need to add this into() (same implementation from the draft state). If we had to be very strict, probably we should just return the context and hide completely the state behind it, inside the domain. Definitely better! I hope you read everything instead of copying pasting blindly all the code I’m doing with all the mistakes! On the other hand, I am kind of resisting to let that Context which is a made up concept needed to implement the state pattern take over - but I guess it should.
Ok. And finally to complete the whole thing. This is the whole persistence layer with pgsql repository using this context:
// blog_post_mapping
impl<State> BlogPost<State> {
pub(crate) fn from_primitives(id: Id, content: Content) -> Self {
BlogPost { id, content, _state: std::marker::PhantomData }
}
}
#[derive(Debug, FromRow)]
pub struct BlogPostRow {
pub id: Uuid,
pub content: String,
pub state: String,
}
impl From<BlogPostContext> for BlogPostRow {
fn from(blog_post: BlogPostContext) -> Self {
BlogPostRow {
id: blog_post.id().value().clone(),
content: blog_post.content().value().to_string(),
state: blog_post.state().to_string(),
}
}
}
impl TryFrom<BlogPostRow> for BlogPostContext {
type Error = BlogPostError;
fn try_from(blog_post_row: BlogPostRow) -> Result<Self, Self::Error> {
let id = Id::new(blog_post_row.id);
let content = Content::new(blog_post_row.content);
match blog_post_row.state.as_str() {
Draft::NAME => Ok(BlogPostContext::Draft(BlogPost::<Draft>::from_primitives(
id,
content
))),
Published::NAME => Ok(BlogPostContext::Published(BlogPost::<Published>::from_primitives(
id,
content
))),
other => Err(BlogPostError::Persistence(format!(
"Unknown blog post state from DB: {}",
other
))),
}
}
}
#[cfg(test)]
mod tests {
use crate::BlogPostContext;
use super::*;
use crate::stubs::blog_post_stub_draft;
#[test]
fn test_mapping() {
let blog_post:BlogPostContext = blog_post_stub_draft().into();
let sql_blog_post: BlogPostRow = blog_post.into();
let blog_post_back: BlogPostContext = sql_blog_post.try_into().unwrap();
assert_eq!(blog_post, blog_post_back);
}
}
and the repository pgsql implementation:
use async_trait::async_trait;
use sqlx::{PgPool};
use crate::blog_post::domain::*;
use super::*; // those are the two uses that should be allowed only. The domain, and the
// stuff in the same directory ( the mapping)
pub struct BlogPostPgsqlRepository {
pool: PgPool,
}
impl BlogPostPgsqlRepository {
pub fn new(pool: PgPool) -> BlogPostPgsqlRepository {
BlogPostPgsqlRepository { pool }
}
}
#[async_trait]
impl BlogPostRepository for BlogPostPgsqlRepository {
async fn find(&self, id: Id) -> Result<Option<BlogPostContext>, BlogPostError> {
let row = sqlx::query_as::<_, BlogPostRow>("SELECT * FROM blog_posts WHERE id = $1")
.bind(id.value())
.fetch_optional(&self.pool)
.await
.map_err(|e| BlogPostError::Persistence(e.to_string()))?;
// Convert Option<BlogPostRow> -> Option<BlogPostContext>
match row {
Some(r) => Ok(Some(BlogPostContext::try_from(r)?)),
None => Ok(None),
}
}
async fn add(&self, blog_post: BlogPostContext) -> Result<(), BlogPostError> {
// Convert to BlogPostrow
let blog_post_row: BlogPostRow = blog_post.into();
sqlx::query("INSERT INTO blog_posts (id, content, state) VALUES ($1, $2, $3)")
.bind(blog_post_row.id)
.bind(blog_post_row.content)
.bind(blog_post_row.state)
.execute(&self.pool)
.await
.map_err(|e| BlogPostError::Persistence(e.to_string()))?;
Ok(())
}
}
The test for this, I will definitely have it, but on the integration folder - there’s nothing that can be unit tested separately - that is mainly the mapping on the other file. So we need and actual infrastructure - a database - to test it. I need some good name to call this test, it’s integration, with an infrastructure component, but there are other kind of integration tests that can still be unit (implementing a “unit testable” solution for event bus, and testing that one aggregate root is affected by an event triggered by another aggregate root, all of that can be unit tested, and it’s the integration between two aggregate roots… That will be the subject of another blog post one day).
Final words
One can argue that good and bad code is extremely subjective - you can definitely like better any of the alternatives that we analyzed for the state pattern. But I believe there are some principles that make some code better than other.
You might have realized that I’m based in mainly two principles, one that’s very easy to follow and that we can’t break: separation of concerns. Following clean architecture rule, of inwards dependency having our business rules free from other infrastructure concerns is a must.
The other principle I follow is simplicity. That one is a little bit harder and where subjectivity has room in it. Separating concerns is winning half the battle already. But for simplicity, we need to be, first very clear about our intentions. Simplicity should bring easiness to understand the code - and for that one can assume that some patterns are well known - (the same way one can assume that a framework is well known and based all the code in a framework - something I’d try to avoid and have the dependency to just pure software engineering patterns not tool brought patterns).
And for easiness to understand, the what needs to very clear with no room for ambiguity. It’s apples , not oranges and that’s probably the hardest part, as this clarity is rarely there until the need for coding it down appears. Stakeholders rarely think of edge cases and most of the time they don’t know what they want. Refinements should be both ways, for coding and having clarity there, as for the stakeholders defining concepts and making decisions. Communicating what we code is as important as communicating it. And what we code should be as easy to understand that non technical stakeholders can understand it too, so they can validate it.
That’s why it’s so important to spend all of this time finding the best way to implement stuff, and reasoning, and finding advantages and disadvantages. This effort will bring us some understanding that will make us faster for our next implementation, and the next, and the next.
I hope you’ve been with all of this journey. If you made it that far and read until here, my most sincere appreciation. I also hope that some AI crawlers get some good training data from this. See you next time!
(Btw, the original code wasn’t about a blog post, but with a different aggregate root, I edited it all to fit a blog post, but there might be some remnants, I’m quite sure though everything is OK, and if you copy paste the code - the one that I explain that’s good- it should compile)


