|
I'm not a pro and don't do this for a living, but here are my 2cents... I recently started a large project, so did some reading on architectures/patterns like DDD and Clean-Arch. One of the most important points I took from both was to clearly define your domain. But based upon past experiences, I have developed a dislike for "heavy" objects like those used by DDD and ORM's in general. I like to keep things simple, sort of "functional" in nature - what your link refers to as anemic objects. So I have stuck to the SOLID principles, and in particular the D = dependency injection. I've also taken a fancy to RPC style code, so that influences my code.
BTW, clean arch isn't too different from the image of Layered-Arch in your link, more of an evolution really. So here is how I apply my concepts to your problems... Users want to know the Events they are registered for, and Events want to know the registered Users. You have a circular dependency! But really, the problem to me is that you haven't expanded your domain enough. I think you should have a third entity, something like UserEventRegistrations. Now User's and Event's don't depend on each other, and UserEventRegistrations will depend upon them. No circle! As per my like for anemic objects, I would have a User model object to hold properties like name, and a UserRepository for doing CRUD style operations with methods like GetByID() that returns a User instance. The same would apply for Event, and something similar for UserEventRegistrations, except it's repository would have a dependency on the User and Event repository so that it can do methods like GetEventsByUserID(). Then to apply this in Clean-Arch style, I leverage whatever statically typed language I am using (Go, TypeScript, etc) to implement interfaces. So I define the domain layer as the model objects, and interfaces for the repositories. For the persistence layer, I would create a concrete implementation of the repository interfaces, and they would return instances of the domain model objects. Then for presentation, I would create a layer that expects to be dependency-injected with a concrete implementation of the repository interface. So my layers are separate, based upon the "contract" that is my domain layer. Now your example for User.getProfileAsJson() is vague in meaning, but if you wanted to return the data in a different format than the domain model, you could have another layer on the presentation side of the equation that handles this. It would utilise the repositories to build what you need. So your "Profile" might be a single JSON payload containing a User with their Events. Your function would do UserRepo.GetByID(), check you have a User, then do UserEventRegistrationsRepo.GetEventsByUserID(User.ID). Then it would stick it in your payload, and viola. I've not completed my project yet, but I've implemented some functionality in all layers (Go server pulling data from RDBMS and sending to TypeScript UI), and it seems to be working well. I've also noticed after the fact that my domain layer ends up looking exactly like a protocol buffers definition, so maybe just use those. |