You're taking a dependency on Diesel - an infrastructure-level library - in your domain layer. That defeats the point of separating domain and infrastructure.
I wonder if it is okay on 12:30 to rigidly define dependency on UserService in `fn new()` of a UseCase. How would you mock it in tests? EDIT: also, how would you architecturally approach changing something in user, or implementing some user-related logic? Like, editing an email with confirmation, which makes user's email take two separate states: - Confirmed(emailAddress: String); - and Unconfirmed(perviouslyConfirmedAddress: Option, newUnconfirmedAddress: String, confirmationToken: String). And then sending a message to the users's confirmed email.
Thanks for the video! It turns out that the trait of the UserRepository, which is located at the domain layer, depends on the newUser structure, which is declared at the presentation layer? Doesn't this violate the order of dependencies in a clean architecture? 🤔
Thanks for the feedback! Yes, by definition, you're correct. However, when following patterns, there isn't always a one-size-fits-all approach. The best method is whatever works best for your specific situation. While introducing mappers and DTOs adheres to clean architecture principles, it also comes with additional maintenance costs. For straightforward use cases like this one, where the models are simple and relational fetching is minimal, it might be more practical to use domain objects directly.
Why didn't you create a trait for Service and just create an implementation for that? "#[async_trait] pub trait UserService { async fn register(&self, new_user: &NewUser) -> Result; async fn find_by_email(&self, email: String) -> Option; }"
Surely, I will create a video on the job market. As far as frameworks go, Axum, Actix, and Rocket are all easy and straightforward to use. Each has its strengths that i will discuss in an upcoming video.
Great job. A detail that he has forgotten. You use diesel and not diesel async for this reason it is recommended to wrap the database operations in a web::block. Example in the Actix documentation. web::block(move || { Obtaining a connection from the pool is also a potentially blocking operation. So, it should be called within the 'web::block' closure, as well. let mut conn = pool.get().expect("couldn't get db connection from pool"); insert_new_user(&mut conn, name) }) .wait? But this can mess up your repository. So we can use tokio::task::spawn_blocking. Good job.
Excellent, I am currently using DDD with Rust, but I have a question at minute 6:45, you call in the Queryable and Serializable domain. In that case wouldn't you be contaminating the domain with the infrastructure?
Thanks for the feedback, Actually, there is no definitive right or wrong way when it comes to models in Domain-Driven Design (DDD). My approach is as follows: Use domain objects directly for GET requests when they match your data needs, as this simplifies your design and maintains consistency across your application. This approach is particularly effective for read-heavy operations, where performance benefits from avoiding unnecessary transformations and keeping the codebase straightforward.
You're taking a dependency on Diesel - an infrastructure-level library - in your domain layer. That defeats the point of separating domain and infrastructure.
that was great, would love to see you do a second with testing and/or error handling.
+1 for testing and domain-level errors (use enums for that to make it bulletproof?)
I wonder if it is okay on 12:30 to rigidly define dependency on UserService in `fn new()` of a UseCase. How would you mock it in tests?
EDIT: also, how would you architecturally approach changing something in user, or implementing some user-related logic? Like, editing an email with confirmation, which makes user's email take two separate states:
- Confirmed(emailAddress: String);
- and Unconfirmed(perviouslyConfirmedAddress: Option, newUnconfirmedAddress: String, confirmationToken: String).
And then sending a message to the users's confirmed email.
youre the best, mr colon! love your channel
Great Video, could you continue with next videos on this content, jwt authen for example?
@@letoan285 Thats the plan.
Great Video
Thanks for the video! It turns out that the trait of the UserRepository, which is located at the domain layer, depends on the newUser structure, which is declared at the presentation layer? Doesn't this violate the order of dependencies in a clean architecture? 🤔
Thanks for the feedback! Yes, by definition, you're correct. However, when following patterns, there isn't always a one-size-fits-all approach. The best method is whatever works best for your specific situation. While introducing mappers and DTOs adheres to clean architecture principles, it also comes with additional maintenance costs. For straightforward use cases like this one, where the models are simple and relational fetching is minimal, it might be more practical to use domain objects directly.
Why didn't you create a trait for Service and just create an implementation for that?
"#[async_trait]
pub trait UserService {
async fn register(&self, new_user: &NewUser) -> Result;
async fn find_by_email(&self, email: String) -> Option;
}"
Hi can you please talk about rust job market as backend developer. Which framework is mostly in used is it axum,actix or rocket?
Surely, I will create a video on the job market. As far as frameworks go, Axum, Actix, and Rocket are all easy and straightforward to use. Each has its strengths that i will discuss in an upcoming video.
Great job. A detail that he has forgotten. You use diesel and not diesel async for this reason it is recommended to wrap the database operations in a web::block. Example in the Actix documentation.
web::block(move || {
Obtaining a connection from the pool is also a potentially blocking operation.
So, it should be called within the 'web::block' closure, as well.
let mut conn = pool.get().expect("couldn't get db connection from pool");
insert_new_user(&mut conn, name)
})
.wait?
But this can mess up your repository. So we can use tokio::task::spawn_blocking.
Good job.
Excellent, I am currently using DDD with Rust, but I have a question at minute 6:45, you call in the Queryable and Serializable domain. In that case wouldn't you be contaminating the domain with the infrastructure?
Thanks for the feedback, Actually, there is no definitive right or wrong way when it comes to models in Domain-Driven Design (DDD). My approach is as follows:
Use domain objects directly for GET requests when they match your data needs, as this simplifies your design and maintains consistency across your application. This approach is particularly effective for read-heavy operations, where performance benefits from avoiding unnecessary transformations and keeping the codebase straightforward.
@@Semicolon10 of course, thank you