Using Separate Read/Write Models with EF Core and CQRS

  Рет қаралды 20,688

Milan Jovanović

Milan Jovanović

Күн бұрын

Пікірлер: 70
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Get the source code for this video for FREE → the-dotnet-weekly.ck.page/read-write-db Want to master Clean Architecture? Go here: bit.ly/3PupkOJ Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
@ttolst
@ttolst 11 ай бұрын
A bonus of splitting your reads and writes like this is that you can now also easily use DBs that support read replicas. AWS Aurora is one example of this.
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
This opens up so many possibilities, if you think about it some more. Appreciate you highlighting the read replicas use case 👌
@twstdp1
@twstdp1 11 ай бұрын
This is incredibly useful for the right audience. Thanks for the content!
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Glad it was helpful! I agree that it depends on the audience 😅
@l0l3r123
@l0l3r123 11 ай бұрын
This might be silly question, but can I just use ApplicationReadDbContext to commit changes to the database? Ignoring the constraints of the DDD models?
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Nothing is stopping you
@ahmedrizk106
@ahmedrizk106 11 ай бұрын
hey Milan, would you consider having two different implementations for a user repository a write and read repository leveraging the new keyed services feature in .NET 8 and you can inject either one of them depending on the use case read/write? would that be simpler than using different models and maintaining them?
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
That would be too much, even for me 😅
@JordiC354
@JordiC354 2 ай бұрын
This is the unique video about splliting model on all internet. Great! I think to implement it.
@MilanJovanovicTech
@MilanJovanovicTech 2 ай бұрын
There should be more content around this
@JordiC354
@JordiC354 22 күн бұрын
How did You achieved which only write model generates Migrations?
@swift8995
@swift8995 3 ай бұрын
How should I configure complex value object with two or more fields inside (e.g. Money or Address) in read model?
@MilanJovanovicTech
@MilanJovanovicTech 3 ай бұрын
Ideally as a flat object, so we can fetch it all in one query
@ridz4576
@ridz4576 10 ай бұрын
Hi Milan, Apart from separation of concerns or SRP, what is the benefit of separating query and commands in separate handlers ? Even if we are separating the handlers, rest service class and repository class are common for these read and write methods. Could you please tell me the actual benefit of separating the Query and Commands ?
@MilanJovanovicTech
@MilanJovanovicTech 10 ай бұрын
www.milanjovanovic.tech/blog/cqrs-pattern-with-mediatr
@TroyGarner-h3i
@TroyGarner-h3i 8 ай бұрын
@3:50 - I like using the type system and would probably compare one of the known configuration types' namespaces to see if it matches the type in question instead of hardcoding a magic string there. Namespaces should in theory match the folder structure of your project.
@MilanJovanovicTech
@MilanJovanovicTech 8 ай бұрын
True, true
@ДерзкийДерзкий-т9д
@ДерзкийДерзкий-т9д 2 ай бұрын
In your other videos I saw that you put Queries and Handlers at the application level and use repositories inside. However, here I can see that you put queries and Handlers at the infrastructure layer, at the same level as repositories. And you work directly with dbContext in handler. How do you make a decision of which approach to use? (Or I probably miss something)
@MilanJovanovicTech
@MilanJovanovicTech 2 ай бұрын
I'm showing different options in some videos. Keeping everything in Application with abstractions lets you abstract away the DB internals. We won't be switching the DB every week, but this is still helpful with encapsulation and reusing common stuff. On the other hand, you could also just integrate with EF directly to keep things simpler. You accept EF as a technical decision that you won't change in the future, and don't abstract it in any way.
@ivan_seleznov
@ivan_seleznov 2 ай бұрын
Do you think it would be ok to create these ReadEntities in the Application Layer so that they could be used directly in Application Layer queries?
@MilanJovanovicTech
@MilanJovanovicTech 2 ай бұрын
Yes
@VoroninPavel
@VoroninPavel 11 ай бұрын
The main challenge is indeed a need to maintain both models aligned. Whenever we make changes in domain model, we are forced to have corresponding changes in read model. There's no type safety here ='(. I'm currently asking EF team to add some features which can make life easier and let reuse rich model for queries without polluting them with unnecessary navigation properties. It is issue #32658 in EF repo.
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Quite an interesting discussion in that issue, I'm bookmarking it to see how it ends
@vano4ok
@vano4ok 10 ай бұрын
Hello! Nice video. So if we have another models for reading, we can change properties to private fields in our domain models. It will make them more protected and business logic will be really in one place. For example your entity has states. You cant check this state in Command so Command cant have decision what to do
@vano4ok
@vano4ok 10 ай бұрын
Besides this. Is it normal to have queries in Infrastructure layer?
@MilanJovanovicTech
@MilanJovanovicTech 10 ай бұрын
So expose the information with a property or method
@BennyS8-q8e
@BennyS8-q8e 11 ай бұрын
I would be grateful for a video of DDD and CA implementation in a DB-First approach project, especially when the names of the tables and fields in the DB do not correspond to the types and properties in the domain
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
That might be an interesting option to explore
@mey33am
@mey33am 9 ай бұрын
Usually we separate read and write models for full text search, pro indexing, fetching huge amount of records, etc. for this need, it’s better to choose another database for read model like Elasitcsearch, choosing SQL Server for read model does not give us any advantage
@MilanJovanovicTech
@MilanJovanovicTech 9 ай бұрын
That makes sense, if you need those specialized DBs
@ChristopherBriddock
@ChristopherBriddock 6 ай бұрын
How would you enable replication, so that the databases are in sync.
@MilanJovanovicTech
@MilanJovanovicTech 6 ай бұрын
That's a database configuration - if it's supported
@ChristopherBriddock
@ChristopherBriddock 6 ай бұрын
@MilanJovanovicTech enabling ms sql replication isn't supported guess I'll just use a raw sql query for now.
@mostafashoja5441
@mostafashoja5441 11 ай бұрын
tnx a lot, i have a question actually it isn't for this post, suppose i have a ddd app and for a insert command, we should many rules that need some data from db , http, queue and ..., and this data will be manipulate in different time and different place, and the key is that this data should check on entity before save, what is the best practice for this condition, add some property in entity and in create entity method fill those(it is bad because use db ,http and ... in entity)?? or in service before call Repository.Add(entity) it is so ugly, for more details we can discuss about it if you prefer using email or social app), thank you so much
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Probably in the service/command handler, right before executing the business logic
@adem_sahin_
@adem_sahin_ 11 ай бұрын
Hey Milan, honestly I struggle to understand the reasoning for this. ORMs are quite useful for mapping rich domain models to database models and vice versa. In your case, those are Value Objects so you can use ComplexProperty method in your entity configurations. And by defining your read models in the application layer, you can fetch data in any form via query handlers in the infrastructure layer. If it's somehow not enough, the old way to achieve this is to create separate domain and database models. IMHO, these options would solve the problem.
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
When you start designing a more complex rich domain model, you realize just how untrue that statement is. Sure, I can map any rich domain to the database, I agree there. Now try to write a semi-complex query with that model. Let's say you've got an Email value object. How would you write a prefix filter on that column? If you configure the entity as an owned type, good luck with that query. If you define it with a conversion - now you have to write Email.Value every time. Now extrapolate that to an object with many properties, and it quickly becomes a mess. How is the example from this video any different from your "separate domain and database models" suggestion?
@adem_sahin_
@adem_sahin_ 11 ай бұрын
Thanks for the answer and the questions, Milan. I'll try to clarify what I mean. Separation of domain and persistence models was a common approach in DDD before ORMs became this powerful. The main idea behind this, as you can imagine, was to keep the domain model ignorant of the persistence layer, which means it doesn't need to be designed or modified based on the database schema requirements. For example, you may want to have some audit info or row version in your persistence model but it has nothing to do with your domain model. These days, those extra information can easily be provided outside the domain layer with the help of EF Core so there is no need for a separate persistence model in many cases. When you have that separation, querying becomes easier since we get rid of all value objects but that's just a side advantage, not the main goal. Mapping from domain to database model on the write side is annoying of course. Because the DbContext works with persistence models, not with domain models. In your answer, you defined a scenario where things can get messy because of EF Core. The question here I would ask myself 'Am I going to make EF Core's problem my problem or am I going to get rid of it because it doesn't cooperate well with DDD in this case?' In the other query, you already answered this actually, which I really liked. You are using Dapper there. That's the answer. Why would we bring extra complexity for ourselves by introducing another dbContext and extra entity configurations for read models because EF Core can't provide us with a simple solution? Now, we have to maintain and keep synchronized two contexts and double configurations. I hope I've explained my hesitations clearly.
@twstdp1
@twstdp1 11 ай бұрын
If you have a simple domain or your database is designed well, this is probably overkill. But I use this in my company because the database structure is complex and we actually use graphql for our client queries. Having a single context which requires complex entities and many navigation properties is generally too much for modeling the domain in a meaningful way. So this strategy is incredibly useful. It is clearly not for everyone.
@maxencemartin6498
@maxencemartin6498 7 ай бұрын
Thank you for this useful trick ! How can you accomplish this without breaking the dependency rule of clean architecture ? (Application -----X----> Infrastructure))
@MilanJovanovicTech
@MilanJovanovicTech 7 ай бұрын
Create an abstraction for anything you don't want to depend on in Infra
@adamlazar2355
@adamlazar2355 11 ай бұрын
Crazy, I was just trying to figure out how to solve this in an app I'm working on. The configurations I set up for the write database were the ones I used for my read database. I have slim application layer DTOs that I use for my read db and a query service that defines the contract for my queries, which is implemented in my infrastructure. My read database is set up with no tracking. But I'm wondering what the downside is to using the same configurations for both the read and write models? I was under the impression that adding a read db context with no tracking would suffice, but now I feel like I've taken the wrong path. Either way, great content. I seem to run into issues, and sometime within the next 24 hours, I get a notification from your channel with a solution to my exact problem 😂
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
They're not the same models, right? So it makes no sense to use the same configuration classes
@Ry4nWTF
@Ry4nWTF 11 ай бұрын
Would love to get your take on this stackoverflow question I posted (5 upvotes, 500 views, with zero response so far haha): I am unsure on whether I should let EF use my domain models or not. Right now I have three types of models: Core.Models (System wide) API.Models (Used only in controllers) Infrastructure.Entities (Only used for persistence) Every method in my handlers/service classes look like this: Get entity from DB Change properties on entity based on request Save changes This works great when using the domain entities, as EF starts tracking the changes and can persist only what's been changed. If I instead map to domain models when retrieving from the DB, so that my services/handlers only work on domain models, my changes aren't tracked of course: Get entity from DB, as untracked, and map to domain model Change properties on domain model based on request Map back to entity Do complex graph diff (set state manually on what has been changed since changes aren't tracked) Save changes The entities contain no data annotations or nothing, I'm using the Fluent API. I see no point in separating domain models and entities. I'm looking at examples from Jimmy Bogard, Steve 'Ardalis' Smith, Jason Taylor, and they are all using their domain models as their EF entities. Are they doing that to keep the code simple for demo purposes or is not beneficial to separate models and entities?
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
If you take a look at my other videos, 90% of them also use the Domain entities as EF models. This video here is an exception the general rule. The reason for using Domain entities as EF models is simplicity. EF can map pretty complex structures into the DB easily. And you don't need to maintain two models.
@DemoBytom
@DemoBytom 11 ай бұрын
The video does not explain the problem you are trying to solve by introducing another layer of code that will need to be maintained. EF Core already lets you setup a mapper from value objects to database types via .HasConversion methods/ValueConverters. The navigation properties problem I do not understand at all - Shouldn't we have database models that include the navigation properties already, and then map them while querying to the domain model? Domain model can easily be persisted using context with navigation properties, even if it does not contain them. So I really do not understand what problem this approach solves. What it introduces though is double amount of database models to maintain, double amount of configuration objects to maintain. It's fine with your example - 2 tables, and one relationship. But a database with 100 tables and many relationships? I suspect you also follow Database First model here, so which context you use for migration purposes?
@Rein______
@Rein______ 11 ай бұрын
Within a DDD aggregate, i use navigation properties. References to other aggregates can be done with navigation properties, or by a ID. I prefer to reference other aggregates by ID. This makes it simple to understand, and prevents unwanted coupling between aggregates.
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
What do you mean by database model and domain model?
@Wouldntyouliketoknow2
@Wouldntyouliketoknow2 11 ай бұрын
Back in the day, it was bad to model your domain model after your persistence layer. EF entities were always about mapping the persistence layer. Its a shortcut and most often a cost effective one to start giving those same entities a dual responsibility of being a domain object. Some more solutions might be: 1. Use a repository pattern and have a base "read repository" that establishes default query behaviours like AsNoTracking. If a query is "difficult" because of using custom types (like Value Types etc) as ef properties then consider removing them.. EF is never going to solve DDD in my opinion and its probably not its responsibility which leads into solution 2) Create your own domain model. For persistence you can inject DbContext and when you save your aggregate it uses EF at that point. When you load your aggregate same thing - however EF entities are mapped to seperate domain layer objects. Yes this is a pain to get right but its the cleanest solution.. for most apps arguably not worth the effort though, but for those apps you probably dont need DDD in the first place.
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
I don't think I ever covered that approach (number 2, in your comment) on the channel. I did discuss the snapshot pattern in a similar context, but I don't think that fits the bill. In any case, it's interesting reading the comments under this video. I know this solution makes sense in a given context. Is it me explaining it poorly? Or do people somehow miss the point.
@Wouldntyouliketoknow2
@Wouldntyouliketoknow2 11 ай бұрын
@@MilanJovanovicTech I think it's just the idea of doubling up on the EF mapping and configuration layer that is not appealing, especially when there is only one conceptual set of database tables behind both.. it's a potential source of headaches and bugs and the gains have to be questioned. I know that managing EF migrations when you have larger teams and stuff going on in multiple branches can be a source of problems at times so adding an extra dimension to that isnt appealing. Especially when in a modular monolith you will also have multiple dbcontexts to begin with. In this circumstance (read vs write to same tables) it's not the database model that is any different in your scenario as in both circumstances you have same tables physically backing both the reads and the writes. Your motivations for the seperate read DbContext seemed to be 1) because you want to express the query differently in the read case without Value Types of the write model getting in your way. 2) you genuinely need different domain object in the read case becuse perhaps you show different fields to write case etc. It should be absolutely possible to have a read domain object vs a write domain object, but EF being an ORM may not allow you to easily express this by mapping two types to the same source tables. In DDD we should be able to model as many domain objects as we like to suit domain problems even if that means we could want 5 or 6 or more etc for various use cases that end up touching the same tables. We don't want 5 or 6 or more DbContexts though. So I think this solution is in the territory of EF workarounds that is highlighting a deeper problem with trying to double up responsibility of the ORM to also be a domain model. However like you say - in that context it works for you and will work for others but I am not sure that overall context is clear.
@sunzhang-d9v
@sunzhang-d9v 11 ай бұрын
Can you provide an example of a modular architecture
@lpsoldier357
@lpsoldier357 11 ай бұрын
I suppose there are 4 tables in total, right? 2 writting tables and 2 reading tables? It'd be cool showing the process of syncing the data between all of these tables.
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
No, it's 2 tables in the database and we maintain a domain and read model in the code
@lpsoldier357
@lpsoldier357 11 ай бұрын
​@@MilanJovanovicTechCool. I didn't see where's the table name mapping. I mean, in EF CORE when you create these 2 mappings: IEntityTypeConfiguration and IEntityTypeConfiguration the tables name would be the same as the class (Follower and FollowerReadModel). If you need another name for its table you can use builder.ToTable("Followers") for example.. That's why I thought there were 4 tables in your example.
@MaxSupercars
@MaxSupercars 11 ай бұрын
@lpsoldier357 Actually it depends on you. You can use also 2 separate databases (also with different tables), one DB for writes and second DB replicated with tableviews for fast access if needed. That's why is Milan showing example of 2 separate models what is base for CQRS. For use of 2 different DBs you use 2 different DB connection strings at DbContext service configuration and for use of 2 different tables you can use explicit table naming in DbContext.OnModelCreating() function -> entity.ToTable("TableName").
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
@@lpsoldier357 I didn't explicitly configure the table names - but it actually depends on the DbContext. I used the DbSet navigation properties with the same name as the other context - Users,Followers. So they're mapped to the same table.
@fifty-plus
@fifty-plus 10 ай бұрын
It is much simpler and much less code to put your context behind and interface and use ISP for read and write segregation when created from your context factory and overriding the save changes on the read context to throw. Maintaining all this code is an unneeded overhead, sounds good on paper but is a bad idea that only gets worse the larger the application grows.
@MilanJovanovicTech
@MilanJovanovicTech 10 ай бұрын
Perhaps, but I just wanted to showcase how one might implement this. It's up to the developer to look at this example in context of their system, and decide if it makes sense for them or not.
@shecodes94623
@shecodes94623 4 ай бұрын
Que o Senhor Jesus abençoe a tua vida, obrigadão brother
@MilanJovanovicTech
@MilanJovanovicTech 4 ай бұрын
Muito obrigado!
@ahmad_9877
@ahmad_9877 5 ай бұрын
"We can't use navigation properties or navigation collections, because something like this is against domain modeling best-practices (with hesitation and uncertainty in the face!)." So why is that?!
@MilanJovanovicTech
@MilanJovanovicTech 5 ай бұрын
@@ahmad_9877 Persistence ignorance, encapsulation
@ahmad_9877
@ahmad_9877 5 ай бұрын
@@MilanJovanovicTech Why the entity that is root of its aggregate must be ignorant of its directly related entities? For the same reason, it is also against the encapsulation. The relation between the entities will be transparent to them and it will be managed outside of them. So how it is encapsulated?
@practical-media
@practical-media 11 ай бұрын
Instead of making so complex videos , giving just the concepts with simple code would so much helpful. 😅
@nicolaimagnussen1914
@nicolaimagnussen1914 11 ай бұрын
It might be that this is an intermediate video, what things did you feel were to complex?
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Then it wouldn't make any sense
@twstdp1
@twstdp1 11 ай бұрын
I didn't find the video complex. The concepts were clearly defined by examples. I appreciate the content.
Refactoring From Transaction Script to Domain-Driven Design
15:22
Milan Jovanović
Рет қаралды 11 М.
Completely Get Rid of Exceptions Using This Technique
19:24
Milan Jovanović
Рет қаралды 27 М.
24 Часа в БОУЛИНГЕ !
27:03
A4
Рет қаралды 7 МЛН
«Жат бауыр» телехикаясы І 26-бөлім
52:18
Qazaqstan TV / Қазақстан Ұлттық Арнасы
Рет қаралды 434 М.
Mastering CQRS in Just 5 Minutes
5:48
ByteMonk
Рет қаралды 4,2 М.
This Is How the Anemic Domain Model Turns Bad
13:08
Zoran Horvat
Рет қаралды 10 М.
Dioxus vs Leptos  | Rust GUI Wars #2
21:18
Creative Coders
Рет қаралды 12 М.
The Right Way To Use Feature Flags in .NET (targeting users explained)
17:44
Your Domain Model isn't your Data Model
8:13
CodeOpinion
Рет қаралды 14 М.
Using Multiple EF Core DbContexts in a Single Application
17:46
Milan Jovanović
Рет қаралды 38 М.
Aggregate (Root) Design: Separate Behavior & Data for Persistence
10:47
EF Core Migrations Deep Dive, Applying Migration, SQL Scripts
16:41
Milan Jovanović
Рет қаралды 18 М.
Tired of Layers? Vertical Slice Architecture to the rescue!
12:26
Domain-Driven Design: The Last Explanation You'll Ever Need
21:05
Software Developer Diaries
Рет қаралды 17 М.
24 Часа в БОУЛИНГЕ !
27:03
A4
Рет қаралды 7 МЛН