4 Comments

You're mixing several concepts here, that are somewhat related but not in the way you present them.

The first one is the notion of DAL (data access layer) is somewhat similar to ORM. It's not. A DAL is an abstraction layer that encapsulates common database operations such as query execution, transaction handling, etc. to be used with a common interface by your upper layer. Your upper layer may be a thin approach (eg. using Repositories or a Table Gateway), a Data Mapper (my favourite), an actual ORM or even something else.

You n+1 query problem seems to be non-existant in most code I see. The only place where this is common is to figure window function sizes. Also, it is quite important to notice, many DB client libraries actually (sort-of) stream the results, so you may not receive the full dataset you selected at once, but row by row. This is also commonly used with cursors, which are used since the early 90's. It is quite important to be aware when NOT to abstract this away, so you won't have to process all the dataset to gather the information you need. Again, proper ORMs provide hydration mechanisms that leverage all this little nuances without a developer having to worry about it.

The way you propose to use attributes is - in my opinion - atrocious. It mangles what could be simple code completion in something to be managed in 3 different places - your database schema, your attribute map, and your actual data container (either object or dictionary). Keeping them in sync for a medium application is a nightmare. I understand your enthusiasm for the gained capability of having an Entity (in this case, a user) that will have data stored in multiple places. However, this is an architectural mistake, and - to be blunt - piss-poor design. In some scripted languages, this will also have the downside of requiring initializing ALL of the dependencies (including connection to external systems) even if you're just checking if the user is active or not, so lazy loading is not possible.

Following on this, on a proper multi-layer application, you would have both a notion of Entity - which in its essence may be just a database row, and a set of services that perform operations on entities. A good strategy to follow to midsize applications is domain:operation:who, ex: storageService->getUserAvatar(currentUser). You may, of course, encapsulate this further into services that compose a set of operations for user, such as sending email, updating avatar, etc.

There are a couple of common architectural patterns I'd recommend you read into, such as onion and hexagonal architectures. They should not be considered gospel, but may guide you into getting a better domain separation on your application design.

Somewhere on the article you seem to also hint at dynamic attributes, a bit "a-la" JSON. There are several ways of doing this, but my usual approach is to keep them separate from the proper schema implementation, and you will *not* want to have attributes such as user.name as a db field and user.favourite_color as a automagically mapped dynamic field, both for performance and maintainability reasons. There are a couple of common patterns/anti-patterns for this, and there is no magic bullet. I've implemented from completely dynamic EAV solutions to having an HSTORE "attributes" field on each relevant table, with some attribute naming in some constants somewhere. Each problem requires a different solution, depending on the usage of the field. If the field is to be filterable (either indexed, or a facet), sometimes it pays off to just handle it "manually" with a caching table.

Then you mention async. I won't get into details on why you're probably using async without any advantage (and probably with several disadvantages), but the main problem is that you mix the concept of async code with network streams. Those are two completely different (but often used together) concepts. I suggest you dig into the reactor pattern for a quick but explicit example of one that is not the other. It is also worth noticing that closures or lambdas aren't necessarily async. In fact, in most languages they aren't - at all.

Expand full comment