Software Architecture Patterns: 5 minute read
orkhanscience.medium.com
orkhanscience.medium.com
So I see "architecture" more as a teaching and communication technique, applied after the fact. Altho since we're always moving between the details and the big picture, having organized abstractions (ie an architecture) in mind will help keep the design clean.
The architecture evolves and sharpens as a system comes together. And this evolving architecture provides the team with a common terminology and overall direction.
But you rarely start by choosing a specific architecture. Unless you already know a lot about your solution.
That's how it probably should be, but in my experience it rarely is. Generally, businesses decide that they need to re-/implement x, then the enterprise architect shows up and decides on the pattern and then the developers are required to somehow make it work, even if it objectively doesn't.
If anything people are too caught up in the idea that there decisions are make or break for a business.
It really depends. Some patterns might be specific to problem domains, but a layered architecture applies pretty much across all types of software projects.
Between defaulting to a layered architecture and just mindlessly piling up ad hoc decisions without any coherent criteria, a layered architecture always wins.
Basically, I've seen SQL in controllers and I don't ever want that again.
Their follow-up book to Software Architecture Fundamentals is also available on O'Reilly - https://learning.oreilly.com/library/view/software-architect...
Ask HN: Are there any openly available software architecture documents? https://news.ycombinator.com/item?id=22011743
Systems Design for Advanced Beginners https://news.ycombinator.com/item?id=23904000
Ask HN: What are good resources to learn system design? https://news.ycombinator.com/item?id=24762734
* How to disconnect the domain model from persistence.
Some solutions add the OR Annotations to the domain model. Some other map the domain model to persistence with a Mapper-layer.
But all of them have some constraints. For e.g. on a web api I receive a dto, map it to a domain model, map it to a persistence model and vice versa. Not much gained imho. In this example I would add the OR-annotations to the domain model and configure many-to-many by "text" so that they are not contained in the domain model.
What are your thoughts?
If you use an ORM (such as Entity Framework I'm about to mention but there are lots) for the write/mutation part then the ORM provides you with that disconnect. You might want to use a different mapper for read, that allows you to write efficient SQL that meets particular needs.
For example, a few years back in .NET Framework I swapped a system that designed/built on Sql Server to run on Postgres. Took me a few days. I didn't change any domain logic during that time. I'd say that the ORM provided a disconnect between the actual persistence and my domain.
I do like a disconnect between my API and the database so that I am able to update the database without causing issues with consumers of my API. That usually requires some kind of mapping but only once. My preferred model right now is a CQRS (don't need ES) using Paramore's Brighter command pattern.
That's for an RDBMS, I think with schemaless/DocDB you can go even further but then the code that loads out the data needs to deal with missing values. That's a different sort of constraint.
I hope that helps, I think I'm rambling now.
All the domain logic is on the entity. So our command handlers (akin to your service methods) look like:
1. Load entity 2. Call method on entity, passing in data 3. Save entity 4. Raise events
So we only unit test 2. because that's the domain code. Loading entities from the dbContext is someone else's code. So is saving. So is raising events.
If your entity class gets big, split out the functionality into other classes so that you end up with a fair portion of bodyless methods.
Hope this is making sense!
But everyone is telling me it's a bad idea, so I'm really not sure. A more convenient way to copy properties over would be nice.
If you add a property which shouldn't be public you have to separate them. For example in the domain model you have a user with email but do not want to expose the email.
Second argument: I usually create a simple struct for each method. For example I do not want, that they provide the id for post requests. I want to send the id on get requests so that they can send an update request with the id. I prefer only "non-null" arguments and as a bonus you get a clean (auto-generated) api documentation.
You can usually configure that out with @JsonIgnore or something like that. Although this works only for this particular (very simple) example and won't help with e.g. flattening multiple nested objects.
Another possible problem: having to support two versions of the API at the same time.
Being disconnected enough so that V2, can load V1's persistence without too much pain is good. Again looking at you office.
Persistence and domain models are fraternal twins, not identical twins. In general recognize that domain model, and persistence model are different things that have slightly different needs. For example the domain model of a person might only care about age, the persistence model is only going to care about DOB. So yeah 95% of the time they are the same and nothing is gained from separating them but having them be too close is often the source of problems.
- DB objects: A poor flat view of those DTOs, for example complex subobjects can be jsonized, or mapped as a foreign key.
Basically it is the role of the DAO to transform the DTOs into DBOs and store them immediately, or read the tables and transform them into structured DTOs. Why? Because the DB representation is always ugly.
So our application works mostly only with the DTOs. The trick is to make the DTOs rich: Adding methods onto them to ease manipulation, and add validation so that a DTO can only exist in a valid state (as much as possible, modulo when they come from the front-end). So it’s not “setDocument(abc)” then “setState(HAS_DOCUMENT)” then “setLastUpdated(now())”. It’s “.addDocument(doc)” and it changes the state accordingly, so the state is always coherent.
So, our DTOs only have Json annotations on them.
And merging DTOs and DB objects? Never succeeded even on a simple app. I wish we could write into tables without copying data into the DB objects.
The more complex your system gets the more you will appreciate this division. However at the outset it just makes things more complex than the situation warrants and it's not clear if the system will ever grow large and complex enough to pay for this expense.
I recommend creating your domain model as a simple wrapper around the persistence model. This way if the whole system never gets complex you will retain 95% of simplicity and if it does get complex you're one refactoring away from making your "shallow" domain model into a full-blown domain model.
Likewise the outbound part of your view/DTO model can be a simple wrapper around the domain model. Inbound models will probably have to be their own classes with extra mapping. The latter might be a bit of a pain (in ASP.NET MVC, for example), however I suspect the pain is instructive - making your inbound DTO a copy of the entire object could be simply the wrong path altogether.
Yes.
> Is that not a forbidden by the layer principle because now the domain model layer has a dependency to the persistency layer.
In my book it's fine. I find that in practice one-way dependencies are ok, so domain->persistence dependency is fine so long as there is no persistence->domain dependency. Simply avoiding loops will take you a very long way.
Which book?
I see know that one can think of the domain model as most changed layer and if there is a bigger change it is because the domain model changes (e.g. business requirements). So a dependency from the domain model to other parts below are mostly fine.
I believe it's used as an idiom.
https://dictionary.cambridge.org/us/dictionary/english/in-my...
You will have a bad time with real software engineering if you take those rules as mandatory. Software engineering rules are like algebraic math postulates, they create common ground that let you explore and communicate some things. Not like legal rules that disallow you from doing something.
Anyway, "layers" imply on single-way dependency. If you have completely independent code, two-way or multi-way dependency, it's something else.
- Maintain a common domain model stored in a shared dll. This is a POCO type without any methods or mappers of its own. All it does is contain all the facts you might need. To encapsulate everything into a serializable structure, we place all top-level domain types as collections into an aggregate type simply called "Domain".
- Maintain mappers to/from the domain model in the locations most appropriate for these to live. These mappers could talk to SQL, memory, some noSQL garbo, the user's web UI, etc.
The advantages with this layer of separation almost universally outweigh the downsides unless you can convince your development team to build a more standardized single-process/monorepo product. In a simpler product, I would be more inclined to just directly interact with SQLite via anonymous types.
We have used ORMs like EF in the past, but these get in the way really badly and prevent you from building your logical model in precisely the way you want to.
The reason being that there's no relevance to the kernel, and modular kernels, also take this approach with replaceable plug-ins or extensions.
"Modular Architecture" is more broad in my opinion and rather a description of internal structure. A "modular monolith" for example is modular but doesn't necessarily have a "core" nor is it required to be extensible with plugins by users.
This is true. I said as much to someone else, in another thread: https://news.ycombinator.com/item?id=29021254
https://dwmkerr.com/the-death-of-microservice-madness-in-201...
Nowadays, people started doing such interesting (although sometimes over-engineered) things with architecture, I couldn't even tell what would be the closest from this list.
If processing units store all data in memory, it doesn’t scale linearly. Maybe sharing is assumed?
I’d like to see some better examples of this if anyone has any!