Skip to content

Keep your project structure simple!

Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


What should your project structure look like? How should you structure and organize your HTTP APIs? Here’s one way by Jono Williams and my thoughts and insights about some misconceptions about Domain Driven Design.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Typical Structures

There are a few common folder structures that people typically use. Probably the most common is organizing by technical concerns. This means grouping files and classes based on their type, such as controllers, services, and models. You’ve likely encountered this if you’ve ever created a web project scaffolding for a new Web Project based on MVC.

Another popular structure is Clean Architecture. In the .NET world, this gets pretty opinionated with various templates you can find on GitHub. Essentially, it’s a solution with various projects to enforce a direction of dependencies. Projects such as Web/HTTP, Application, Domain, and Infrastructure.

Lastly, there’s Domain-Driven Design (DDD) combined with Vertical Slice Architecture, which organizes files by features while still “applying” to DDD, kinda. I say “applying” because there’s always been this emphasis on the tactical/technical patterns, which isn’t really the point of DDD in my opinion.

Looking at a project structure doesn’t imply if someone is using DDD. For example, you could be looking at a Clean Architecture and see the Domain Project. Does that mean they are “doing” DDD? No. IT’s not dictated by the patterns you’re using. Just because there are classes called Entities, does not mean that’s actually anything more than a class used as a data bucket.

In other words, if you have Domain Services, Repositories, Entities, or Value Objects, it doesn’t inherently mean you’re doing DDD; you’re just using a bunch of patterns and concepts. You can be using DDD and not applying any of those patterns or concepts.

If you haven’t already, I highly recommend watching the video to get more context with more illustrations since I’m reviewing someone else’s video.

Keep it Simple?

Here’s a sample project structure that Jono created and used in his video. The basic structure is this.

In project structure above, the Posts/Endpoints is really the focal point where actual behavior lives. An endpoint contains everything needed to fulfill a request. Here’s an example of CreatePost

It’s simple. It’s basically taking an HTTP POST request and providing am means to handle that request and generate a response. It’s using a EF Core (ORM) Database context directly.

No repositories, no domain entities or aggregates, no abstraction, not useless indirection. It’s simple.

It’s kind of refreshing to see a sample that isn’t pattern-driven. The problem with samples is that they often show patterns without enough context. It’s difficult to create a sample with enough complexity that warrants using certain patterns. So it’s left to the reader to recognize that the pattern is being applied in a trivial use case. This is exactly the problem people often fall into with the tactical patterns of DDD and try applying them when they don’t need to.

Likewise, you may try the simplistic approach above when your use case is much more complex.

Complexity

Here’s another simple example of being able to “Like” a blog post.

Now, complexity might arise because of scale. If it does, we might need to start applying other patterns to manage the complexity. For example, if we have a lot of likes and we need to show the number of likes on a post, currently, the only method would be to count the number of rows in our Likes table. Depending on our scale, that might not work efficiently enough for us.

We might choose to persist instead a calculated value of the number of likes on the Post itself called LikeCount. Then, we can increment the LikeCount value of the post.

Also, we’re publishing a PostLiked event, so we can use the Publish-Subscribe pattern with an Event-Driven Architecture to independently/separately notify the post’s author that someone liked it.

But what I’ve just done is created the possibility of consistency issues. The persistent calculated value of the LikeCount must always be updated. If you add a new like in any other part of the application through any new route, you’d also have to make sure you’re updating the LikeCount on the post and publishing the PostLiked event. In other words, adding a like can only occur through this existing code.

This is where you might want to consider applying other types of patterns so you can encapsulate all the required behavior when a post is Liked rather than just pure data models and a transaction script as it originally started.

Start Simple

What should your project structure look like? Don’t start with assumed complexity.

Domain-driven design isn’t about patterns. It’s about understanding your domain and capturing concepts in code. If you understand the domain, you can choose how to handle its complexity if it exists. Using a data model and transaction scripts where appropriate is still doing Domain-Driven Design because you understand that that portion of the domain, maybe it’s more of a supporting role, calls for it.

It’s typical to hear people say that Domain-Driven Design is way too complicated, and it made my project hard to manage. However, my assumption in most of these claims is that they are trying to use the DDD tactical patterns and concepts to solve problems that they do not have. If you apply patterns that aren’t needed, yes, you’ll be making things more complicated. Use patterns or concepts to solve problems you have.

If you don’t have a lot of domain complexity that you’re trying to capture in code and are more driven by data and CRUD, then use data models and transaction scripts. That’s fine. Some say that anemic domain models are an anti-pattern. I beg to differ. It’s only an anti-pattern when creating a rich domain model and not capturing any behavior. It’s equally an anti-pattern to try and use patterns for problems you don’t have.

Join CodeOpinon!
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Leave a Reply

Your email address will not be published. Required fields are marked *