Domain Driven Design (DDD) Is Not Programming

Written on 23 November 2015

I keep saying that design and programming are sides of the same coin. This means that both are required to develop a proper product but it also means that they are different . As the name implies, Domain Driven Design is design, therefore it's not programming. And this is quite important because programming, as a mindset, can be and it is an obstacle in doing DDD properly.

First and foremost, DDD means strategic design aka high level design where we don't care about implementation details like code, database, monolith/micro services etc. Sure, DDD has tactical patterns which help us in writing the code, especially doing low-level (object, function) design but the DDD tactics are still higher level than code.

The power of DDD (the strategic part) comes from understanding the domain i.e understanding the business mindset, language and solutions. It's not about coming up with solutions (the programmer mindset), but understanding how the business implements its own solutions. When we do Domain modelling, we open our eyes, ears and brain and we learn about the business concepts and behaviour. It has nothing to do with code. At this level, concepts are just a name(not a class) and behaviour is how the business uses those concepts (not a method/function).

It's also important to understand the domain relationships between the concepts. Those aren't RDBMS relationships (one to many), nor OOP relationships (is a or has a ). It's not about applying programming principles, it's not about identifying programming relationships (inheritance, composition) to be written in code.

For example:"A table has 4 legs". In domain speak this might mean that the "Table" concept requires 4 legs in order to be a valid concept for a specific use case. It's not about a Table class which has a "Legs" property, although it might be a valid way to implement it in code (but at this stage we aren't concerned with writing code, we care about understanding how the business sees (defines) that concept in a certain scenario) . Another example: "A person has a hat". Applying the programmer mindset is wrong, you'll end up with a class Person that has a property "Hat". But from the domain point of view it might mean that "a person owns a hat" and/or "a person is wearing a hat". Domain relationships != object/table relationships. Domain language != programmer language.

The whole point of the DDD strategic design is to identify the business concepts, their constraints and the business behaviour (use cases and business rules), it's not about writing code i.e designing class diagrams or worse, databases. Sure, we need to implement all those in code (our final objective), but first we need to have a very thorough understanding of the things, only afterwards we begin writing code. Basically we need to become domain "experts". And the programmer's mindset is an obstacle in our way.

Even the DDD tactics are higher level than code. You can think of them like this: strategic design is the highest level and it involves learning the domain mindset. Next we're using the tactics (aggregates, entities etc) as an intermediate step, where we're trying to focus and group together the relevant concepts and business behaviour aka business rules, for a specific use case. Only after that we can go implementing things into code, after you know the aggregates.

The DDD tactics are themselves design abstractions, not implementations. The aggregate is a group of concepts (not objects!) and business rules, required by the business process to maintain its consistency when you want to change things. In practice, you have a bunch of names and rules. Those can be implemented by code using your favourite approach (OOP, FP or both). Always, the actual code is an implementation detail (this is the programming part), but we need the proper high level design (concept abstraction) first.

The whole point of DDD is to help us coming up with a model that mimics how the business looks at the things, how the business works in the real world. This will allow us to implement business changes faster, because the code itself is designed (modeled) according to the business mindset (efficient or not). When the business change in a certain way, the code just follows. If we design things programming driven, it will always be a mismatch between the real business model and the coded model, resulting in slower, more complicated changes. Yes, your code could be of the highest technical quality and your algorithms the most efficient, these don't matter if any change required by the business means you have to resort to patching, cutting corners, technical debt or delivering value very slow.

We don't build software for the glory of technology (most of us anyway), we build a product that delivers value to its stakeholders. That's why in DDD, business (domain knowledge) comes first, code comes second. Rushing to write code usually means ending up with an improper domain model which on the long run becomes more and more complicated and harder to maintain. I cringe when I see people asking how to code things in DDD, it's not about the code, it's not about learning a new magic recipe, it's not about being a (better) programmer. That's why, when we're doing DDD (and high level design in general), we should forget we are programmers. The programmer mindset of finding solutions is an impediment when trying to learn how an existing system (a domain) works. The best quality of a DDD practitioner is listening, not problem solving.

P.S: Obviously, all the good modelling and design is useless with a crappy implementation, but the point here is: understand business first, understand business second, write code third.

I Saw Your Profile On Stack Overflow

Written on 20 November 2015

So, I get this glorious email

[bla bla] viewed your Stack Overflow Careers profile and sent you a message.

Hi Mihai,

I saw your profile on Stack Overflow Candidate Search and wanted to get in touch with you about some development job opportunities we have here at [bla bla].

You can see our current job ad at!-[bla bla].aspx and your profile suggests that you may be interested in hearing more about the type of development work we do at [bla]. If this is not the case then please let me know and I will be sure not to contact you again.

I'd love to have a chance to chat with you to tell you more about bla (you can also find out more at our SO company page) and to find out a bit more about you.


[whatever] VP & Director of Engineering, [bla bla]

Obviously, I was delighted. After all these years on SO and around 300(ish) answers about Domain Driven Design, application architecture and design patterns, finally someone considered that I'm a good material for a junior/intermediate dev. After all, as mr VP said, my profile ("SOLID web apps architect") suggests that.

Anyway, I was curious about the ad itself and it was so great that I had to blog about it.

We're looking for both junior and intermediate resources;

Yeah, I don't know if an overhead or a C-level asset is the author. Hey, at least they don't look for Chuck Norris or Neo, like other companies do.

Things we’re looking for in terms of more specific skill-sets and experience:

BS, MS, or PhD in Computer Science or related technical discipline (or equivalent).

I only have a BSc degree in Business Administration (Masters degrees were boring so I've quit - 3 times actually) so I guess I'm part of the "or equivalent".

Extensive programming experience in a language that demands strong OO skills (C# / Java / C++ / etc.)

Ok, I've heard about some of those, but I've never heard of this "etc" language. Nevermind extensive experience. I'm still puzzled though about which language/stack they're actually using. I mean, writing web apps (job is for a cloud solution) in C++ is pretty old-skool, right? Maybe they're using all the languages? I wonder how we do memory management and pointers in that "etc" language.

Experience working with scripting languages like PHP, Ruby, Python or Perl in a web application.

Lol, at least I've heard of those, hey I even did php for some years. But Perl? Really?! I'd get that "etc" language before Perl. A curious thing though, how Ruby and Python aren't part of the OOP list above. I guess they're just some silly scripting languages where strong OOP skills don't matter. But now I'm really confused about their stack. Maybe C# with a dash of Ruby and a pinch on Perl? Or that "etc"?

Coding skills in Javascript / AJAX, database design and SQL.

Phew... at least they don't require experience with these. I do have to wonder why db design and sql are next to javascript/ajax. I think they're related somehow.

Several years of software design and development experience, with knowledge of Unix / Linux.

How many is "several"? Enough to qualify for a junior/intermediate? I don't know. Hm... Linux. Oh well, at least they know that 5 will run very well on it.

Ideally you’ve been coding since you were old enough to read.

Whoa... let me see... I've learnt how to read when I was 7, this means that ideally I'd have close to 30 years coding experience? Not as much as uncle Bob, but well, it's for a junior/intermediate position after all. Oh wait, maybe it's for the younger people, those who've just graduated college. 23 - 7, eh, only 16 years experience for the ideal junior/intermediate candidate.

A solid foundation in computer science, with strong competencies in data structures, algorithms, and software design.

You know, someone really needs to be more specific. What does count as solid foundation? Strong competencies? If I implement bubble sort, does this mean I'm competent in writing maintainable code? It reminds me of another very used and useless requirement like "analytical thinking" or "problem solver". Btw, is any company looking for someone who doesn't solve problems and (s)he's clueless about their field? I know they hire those people, but are they actively looking for them?

(Intermediate level) Previous experience with agile development methodologies and unit testing.

Wait a minute.... all those things, like extensive experience and more experience and ideally at least 16 years of programming is just for the junior position? Wow, these people are hardcore! But really, a junior of that level who doesn't know about agile or unit testing (cough)TDD(cough) is not such a great resource, is it?

I'm very curious about a senior/lead or architect position. I guess it's someone with at least 50 years experience in building scalable apps and former senior architect at Facebook/Twitter/Amazon and the founder of that "etc" language.

For the obviously clueless author of the job ad: you need at least a MS (masters - not to be confused with Microsoft) in basic logic and a PhD in common sense. Better yet, go to a real developer to write the job ad. Demanding extensive experience and strong skills for a junior/intermediate position is an oxymoron. Not specifying the actual tech stack (you know the languages, frameworks and platforms used by the company) shows you don't care enough to attract the right people. Because a Ruby developer won't be in a hurry to become an dev. Good developers are language/tool agnostic but we all have our preferences and things we don't like. For example, you won't see me writing Vb.Net or Ruby.

And yet, you don't have any problem writing this bullshit:

But, for the lucky few—the ones that have talent, the ones who can see beyond fossil fuels—these have the chance to work in an evolved and developer-focused culture.

First, dafuq means "who can see beyond fossil fuels"?! And a culture so "evolved and developer focused" would let a damn developer write the job ad. Who would specify the language/framework used.

I'm pretty certain it was not the VP & Director of Engineering who sent me that email (at least I hope so). Just matching some keywords, the way 95% of 'recruiters' work, shows you simply don't give a fuck. And when you get no answer or an angry email, you complain that there is shortage of good developers and it's oh, so hard to attract talent. It is... when you're oozing laziness. Developers, especially good ones, are pretty smart people with strong analytical skills. They can detect an incompetent recruiter/email/job ad. Unfortunately, most of the job ads are dumb, choke-full of bullshit. Even the decent ones still have some WTFs ("Expert knowledge of RDBMS concepts, Database coding techniques [I suppose it means Sql, I sure hope they don't want someone to code a database] and store [sic] procedures"). And I've read job ads with incomplete sentences and misspelled tech names. The people writing those ads display a total lack of respect and competence, showing how little they care.

Yes, it was a rant and I'm sure that you (a developer reading this post) read and mocked similar job ads. But, if you are one of the overheads (HR) or C-assets (VP/CTO) reading this, show some respect and common sense and try to understand the mindset of a good developer. Hint: ego stroking, big empty words aka bullshit, vague specifications and stupid technical requirements will attract only juniors or mediocre developers who are very passionate about having a job but that's about it. Want to attract great talent? Then think and act like one.

Introducing SqlFu 3

Written on 02 November 2015

I'm happy to report that SqlFu 3 finally got to a stage where it can be publicly usable. Although an alpha, it's more of a pre-beta, as the features I wanted are all in. But things can change, so that's why it's still an alpha for now.

Warning! Version 3 is NOT backwards compatible.

It's not an ORM

At least in SqlFu case, "micro-ORM" is the wrong name. SqlFu is a data mapper (maps data to commands and rows to objects) with productivity helpers. While the objective of an ORM is to pretend Sql doesn't exist and we're all having fun using objects, in SqlFu we think sql and we're writing sql, but many of the most common operations are provided as strongly typed helpers (which are NOT Linq, but specialised string builders). A bit of abstraction exists, but it's merely a side effect, not an objective.

Objects are just data source or data destination.

Designed for versatility

I've learned a lot from the previous versions, issues posted by users and my own usage especially in a cloud environment. It's not about adding gimmicky stuff, but actual value that make things easier. For example, two of the new features are: support for working with multiple databases and transient error resilience. Nothing fancy, but it cuts down repetitive boiler plate code.

New Features

  • Multi database support
  • Transient error resilience
  • Strongly typed builders to create a table or to build dynamic queries for a table/view
  • Sync/async support for any operation
  • Any helper command can be customized
  • SProc support (command and queries)
  • Mapping to anonymous objects
  • Faster mapping to dynamic


Obviously, get the pre-release version from nuget. Only SqlServer 2012+ (including Azure) is supported.

//app startup
SqlFuManager.Configure(c =>
               c.AddProfile(SqlServer2012Provider.Instance,"[connection string]");            

//factory, register it in your Di Container
var getDb=SqlFuManager.GetDbFactory();

 public class SomePost
         public int Id { get; set; }
         public string Title { get; set; }
         public string Content { get; set; }
         public SomeEnum State { get; set; }
         public DateTime CreatedOn { get; set; }

     public class Category
           public string Name { get; set; }
           public int PostId { get; set; }

//anywhere you need it, we inject a factory, NOT a connection
class SomeService{
   public SomeService(IDbFactory getDb){ }


//let's generate a create table statement. This is NOT mapping, just a builder
// only default types are abstracted.

using(var db=_getDb.Create()){
  db.CreateTableFrom<SomePost>(cfg =>

                   cfg.Column(col => col.Id, c =>
                       .Column(col => col.Content, c =>
                       .Column(c => c.State, c =>
                       .Column(c => c.Title, c => c.HasSize(75))

   db.CreateTableFrom<Category>(cfg =>
                   cfg.Column(c => c.Name, c => c.HasSize(70))
                           c =>
                               c.Columns(e => e.PostId)
                                   .Reference(f => f.Id)

//want to execute a create table as a task?
public class Creator : ATypedStorageCreator<Category> //implements ICreateStorage
        public Creator(IDbFactory db) : base(db)

        protected override void Configure(IConfigureTable<Category> cfg)
            cfg.Column(c => c.Name, c => c.HasSize(70))
                           c =>
                               c.Columns(e => e.PostId)
                                   .Reference(f => f.Id)

//register all types implementing ICreateStorage into a di container
// then invoke all storage creators
//using autofac

//transient errors resilience, if connection times out or max number of connection is reached
//operation is automatically retried for 10 times with a delay between retries
// works with the db factory only (not connection)
_getDb.Do(db=> {

Operations example

  var func = db.GetDbFunctions();

 //build query but map only the first column
  db.GetQueryValue(t => t.From<SomePost>().Where(d => d.State == SomeEnum.Last).AllColumns());

//simple insert
  var id=db.Insert(new SomePost() {Title = "my title"}).GetInsertedId<int>();

//custom insert
db.Insert(new {Id = 1, Name = "foo"}, c =>
                    c.CmdOptions = cmd => cmd.CommandTimeout = 500;

 // select  count([Id]) from [Posts]
 //build query but the outcome should be a different type
  var all = db.GetQueryValue(f => f.From<SomePost>().Select(d => func.Count(d.Id)).MapTo<string>());

//select  count([Id]) as Total from [Posts]
//group by [CreatedOn]
//order by [CreatedOn]
 var rez= db.Query(f => f.From<SomePost>().GroupBy(d=>d.CreatedOn).OrderBy(d=>d.CreatedOn).Limit(10,0).Select(d => new {Total = (int)func.Count(d.Id)}));

 //simple update
  db.Update<SomePost>().Set(d => d.Title, "changed").Where(d => d.Id == id).Execute().Should().Be(1);

//anonymous object update
  db.Update(o =>
      o.TableName = "Posts";
      o.DbSchema = "dbo";
  }, d => d.FromData(new {Id = id, Title = "other"}).Ignore(i=>i.Id)).Where(d => d.Id == id).Execute();

 //simple pagination
  var paged = db.FetchPaged<SomePost>(c => c.Sql("select * from Posts"),new Pagination());

  db.DeleteFrom<SomePost>(d => d.Id == id);

//stored procedure
var result=db.QuerySProc<dynamic>("spTest", new {inputParam = 46, _outParam = ""});
var foo=result.OutputValues.outParam;

//value object mapping
//email is automatically converted to string when insert/update and from string to email when select
SqlFuManager.Config.MapValueObject<Email>(/* from*/ e=>e.Value,/* to */o=>new Email(o.ToString()));
db.Insert(new EmailStore() {Data = new Email("")});

This is a very quick preview of SqlFu 3, I hope I'll have the time to write some proper docs. In the mean time feel free to play with it and to report any bugs.

The Unit of Work and Transactions In Domain Driven Design

Written on 02 September 2015

As a principle, the Unit of Work (UoW) pattern is about ensuring consistency by persisting a group of changes as a unit (all at once). Basically, it's an all or nothing operation and the point of this is we don't want to end up with inconsistencies. This is the main idea and it's implemented by a db transaction or the ORM's DbCOntext, ISession etc which themselves in the end will use a db transaction.

As you can see, the UoW is very useful and very persistence related. The problem is many devs trying to do DDD, end up needing something that surely looks like a UOW. However, its not.

Let's take a very simple example, the old 'I want to move an amount from one account to other' which from the Domain point of view is a domain transaction, which involves 2 instances of the same aggregate (the Account). It doesn't matter if the aggregates are different, what's important is that for the transaction to complete, we need to change 2 aggregates. This looks very similar to the Db's BeginTransaction -> Commit usage and naturally, devs consider this should be a UoW and try to implement it.

And that's how we end up with abominations like the UoW interface which 'holds' all the repositories that would be involved in the transaction. Too bad those devs are trying to implement a domain use case with a persistence related design pattern.

Here's the thing, first of all, in a real world business process (a sequence of chained business use cases) there's very little of the 'all changes or nothing' mindset. Further more, a business process can involve different bounded contexts (BC) and can run for days until it completes. Using the UoW pattern is a very naive solution and worse, from a technical point of view, it complicates things a lot when you're dealing with different db types or a distributed app.

Obviously, we need consistency in our process and at the end things will be consistent, but not immediately. As I've said above, a business process can be a series of use cases. Consistency is achieved when all use cases involved in the process are completed. One at the time. Because, in the end, it's all about manipulating aggregates, and the only thing we need to care about is maintaining the involved aggregates consistency.

If you look at the real world, you'll see that very few things are immediate consistent, and 'thing' is the proper term, because all real world processes are eventual consistent, meaning they achieve consistency one operation at the time. Only whole objects are immediately consistent, because otherwise they wouldn't exist. Picture a chair (with 4 legs), if we remove a leg then it's not consistent anymore and it becomes a broken (not valid/usable) chair. The chair needs immediate consistency for it to exist as a valid object.

In DDD, the concepts are implemented as aggregates, therefore only aggregates need to be immediate consistent. But any use case regardless of how many operations it has is basically a smaller process which is naturally eventual consistent.

Btw, when dealing with a business process involving more than one BC i.e involves aggregates that are in different BCs, first of all, it means that we have at least one use case per bounded context. Each use case will publish domain events that will be used by the interested BC to 'trigger' their use cases. If at the end, we need to publish a notification that the process ended, then you're dealing with a saga i.e a long running process.

From a strategical DDD point of view, a domain use case encapsulates one BC specific business operation. This means that a domain use case is limited only to the model of the containing BC. If your use case needs to use an aggregate which is a part of another BC, then the modelling is wrong (either the use case or the BC).

So, a domain use case implementation is responsible to keep each involved aggregate consistent, but one at the time and this can be a problem from a technical point of view if the app goes down from whatever reason. That's why we need to ensure that a started use case/process needs to complete and we can use a durable service bus for that purpose (which will re-execute the use case). And we also need every use case to be idempotent in order to prevent duplicate operations and this means that every contained aggregate change needs to be idempotent as well. The basic principle here is that we achieve idempotency by making every component idempotent.

In conclusion, a domain process or a domain use case should be considered as being eventual consistent and thus the UoW doesn't apply here. But aggregates need to be immediately consistent and thus, the UoW is usable as a repository implementation detail. But only for one aggregate (one consistency group) at the time.

P.S: The "transferring money from one account to another" example gives the impression that somehow this is a 'all or nothing' type of operation and we need to change both accounts in the same time to keep things consistent. Not really. It's still an eventual consistent operation (actually there are 2, one for each aggregate) and if the use case is interrupted midway because of a technical reason, it's not a problem, because the aggregates themselves are always in consistent state. What is 'inconsistent' is the use case itself, because it hasn't completed yet, but this is a non issue because a use case is technically a service, so it doesn't have its own persisted state therefore there's no consistency to care about. Other use cases depending on our case completion will wait for the 'TransferCompleted' event in order to start.

An In-Depth Look At CQRS

Written on 01 September 2015

While in theory CQRS simply means that the model for reads(query) is different (a simpler version of) than the model for writes(command), in practice things are a bit more complex. Add Event Sourcing and CQS into equation and things become quite confusing. But first things first.

Know Thy Enemy

You shouldn't use it just because you've heard it's cool and this how apps should be developed. You need to be aware of its benefits (and its drawbacks). The main benefit is that it enables an app to perform very fast reads while having a maintainable domain model. The drawbacks are that it requires a bit of experience in order to be implemented properly and it adds more technical complexity. Maintaining 2 models (and the read model can be further specialized into more models) adds more code and might require a certain infrastructure. That's why it might be overkill for a glorified db UI app, like many CRUD apps are.

CQRS is a different concept than CQS

CQRS not only has a very similar name to CQS but it's actually inspired by the latter. But what's CQS?

CQS (Command Query Separation) is a design principle which states that an operation should do either a Write (Command) or a Read(Query) but not both in the same time (we`re talking about high level here, not a low level implementation which can do both read and writes). At first it was a principle used when defining functions, but it can be used as an architectural principle too: the application is "partitioned" into Commands (services that change something) and Queries (services which only read the model). It's great for maintainability, because each command/query encapsulates one use case.

If we're using a message driven architecture then we're gonna have commands and command handlers and queries and query handlers. Obviously, in a DDD app,there will be events and event handlers too, but those are have nothing to do with CQS. Note that a command/query handler is just an implementation detail of the Command/Query part of CQS. Basically, I can decide that all my command services will be implemented as command handlers, in order to take advantage of a service bus. Again, this is an implementation detail.

These have little do to with CQRS which deals with a Command model and a Query model. To clear up the confusion you can consider than CQRS is about separate models while CQS is about separate behaviour responsibility (although these can overlap).

To make things even more fun, CQRS and CQS work very well together, however they're still different concepts and should be treated as such. So, an app can use CQS without touching CQRS and vice-versa. However, in practice, especially in DDD apps, they're often used together in combination with a message driven architecture.

CQRS Can Be Used With Event Sourcing (ES)

...but one doesn't always imply the other. And speaking about events, an event driven architecture or the Domain Events pattern doesn't imply ES. They're all different things that are used together because they have great synergy. In probably 99.99% of cases, an app using ES will also use CQRS (and a message driven architecture), because it needs a queryable read model, different than the even store model (hence CQRS).

CQRS: The Command Model

Well, "command model" is not a very accurate term, because in practice there are 2 models: application(as in non-persistence) and persistence. For now on, when I'm referring to the application, I'll say "business or domain" because this is where things make more sense. But it's good to know that we're talking about principles that work and can be used outside the domain layer (if you're thinking in layers).

So, we have 2 points of view of a command model: the model which encapsulates business rules and the model used to store the aggregates (business objects). The command application model is always about enforcing business rules. This is the 'behaviour rich' domain model. It's used only for changes and it defines the "domain model".

But we do need to persist/restore this model so we need a command persistence model fit for this purpose. And you can store it in way you like, however the easiest ways is to use an event store (if your using ES which I highly recommend for DDD apps) or to just serialize the entities or use a document database. Obviously, you can use an ORM, but this is the complicated solution, you'll need to define mappings for each entity and tell it how to deals with Value Objects and other encapsulated details that might be required.

As I've said, it's complicated and it's the least maintainable solution. A generic storage like the event store or a document db, basically anything that doesn't care about your entity structure is the desired solution. And once you have that storage it will work with any entity automatically and in a way it's a kind of a generic repository, but the good one, not the anti-pattern abstraction on top of the ORM one.

We can get away with a generic, non-queryable store because we only need basic CRUD functionality from it: Add, Get, Save, Delete. For the use case which require more than one entity, the the service will use a domain query which will use both the query persistence model and command persistence model to do its job.

CQRS: The Query Model

As with the command model, we still have the query application model and the query persistence model. I'll start with persistence because it's the easiest one: this is your queryable model plain and simple. With a RDBMS this is where you're writing SQL, doing joins and other fun stuff. With a document db you might have query indexes. You can also have very specific models i.e serialized pre-computed view models or query application models. Pretty much anything that will allow you to query things fast.

The application model has the following 'feature': it doesn't contain/enforce any business rules. It can use encapsulation and can be immutable and even have some technical behaviour (methods), but nothing domain related. In many cases it's just a data structure or a DTO (Data Transfer Object). If we're using CQS, then the query application model is in fact the query handler and its result.

Note that a domain (which is the Command model) has queries. However in this case, it's the CQS principle at work and the query service can use a query persistence model, but it can return business objects. This is a matter of using CQS 'inside' the Command of CQRS. It is a bit confusing but if you understand that these principles are different yet they work together, then you won't have a problem.


It's always important to understand the context we're working on. You might call these layers or components, vertical slices etc. but the important part is that they're a criteria to group related concepts and use cases. And we can mix CQRS and CQS. So we can have an UI Query model which consists of query handlers querying a persistence read model and returning view models (whole or bits of it). An Application context with Commands (Register Account, Login etc) and Queries (GetUserForLogin, GetUserRights etc) or a Domain context which has their very own Command Model and/or Command Handlers as well as Queries (CountVipMembers, GetMembersWithoutOrders etc).

As a fun fact, once you see the app like a group of contexts which themselves are a group of use cases, the traditional 'layer' approach doesn't make much sense anymore. It's all about specialized model for changes or for reads and clean services which either change something or read something. And these lead to a better (more maintainable and scalable) architecture but that's a subject for a future post.

One more thing, there are no rules with CQRS besides the separation of the write/read concerns. What matters the most is the principle and the problem it solves. In some use cases, applying the principle means there's no clear separation between the writes and the reads from one point of view (you might be using only one persistence model). This doesn't invalidate the whole principle, it's just a local decision to implement things differently. As long as the app is maintainable, there is no problem. Like many other principles, CQRS is not a recipe with a strict "how to" dogma. But everyone using CQRS should first understand it properly before deciding on a specific implementation.