3

I'm trying to design a simple way of abstracting away data persistence behind an interface, but I'm having a hard time figuring out how much fine grained control should be exposed to higher layers.

Limit, Filter, Order, etc.

Defining an interface for a dumb data repository is all fine and dandy for the basic CRUD operations, but what about all the other common database capabilities such as result filtering, limiting, map/reduce and other transformative or conditional operations? Do these types of operations violate the single responsibility principle in the sense that a persistence layer should just be a dumb retrieve/persist black box -and consequently, would this functionality be better implemented on the calling code side (i.e, in a service layer, or even client side)?

Client side transformations

Although it would greatly simplify the persistence layer, personally, I feel that it would be quite wasteful to perform these operations client side. For example, regardless of whether a user queries for a collection of 10 resources or 1000, the database would have to return the entire data set incurring bandwidth and caching costs, among other things. Also many of these queries are probably heavily optimized natively, and this would throw all of that out the window.

A smart database abstraction

On the other hand, if I were to expose this database functionality in the persistence layer, it would greatly increase the complexity of the interface and it would probably just end up looking like a 1:1 wrapper of the database driver (which is handy for mock testing purposes I suppose, but kind of defeats the purpose of writing the abstraction in the first place).

My question is: is there a general rule of thumb when designing a database abstraction layer for what functionality to expose and what to keep hidden?

  • 4
    Are you doing the abstraction as a framework and are looking for reusablity or do you have a concrete application (domain)? – Thomas Junk May 22 '16 at 17:10
  • Since most databases offer disjoint functionality, if you want a general abstraction you're going to be stuck with the lowest common denominator. What do you actually need to accomplish? – hyc May 22 '16 at 18:01
  • Some measure of reusability across domains would be nice, but my main goal is to encapsulate persistence to reduce coupling across concerns as much as possible. But i'm conflicted because the more database functionality gets exposed, the tighter the coupling becomes until the higher layers of abstraction pretty much have full knowledge of the underlying implementation details. Now that I think about both of your questions a bit more, the gist of it is that I want to have my cake and eat it too... I guess what I'm looking for is a good compromise somewhere in between. – Anchor May 22 '16 at 23:32
  • I don’t know, maybe there are some better ways of exposing functionality without explicitly defining an interface around it, maybe using things like dependency injection and variadic arguments with sane defaults; where the functionality is there if you need it, but hidden if not. Not sure if that’’s a DDD anti-pattern or not though. – Anchor May 22 '16 at 23:34
  • Have you considered an ORM? – JeffO May 23 '16 at 14:52
5

Database systems can implement very different approaches. Just compare SQL vs. NoSQL. And for the latter compare key-value stores with graph databases. So you'll not find a silver bullet that will offer a single API with all the benefits of these databases. So you'll have to narrow down your scope.

Considering the limits, filtering, ordering, many of those are related to the queries of the database. So I think that you definitively need to take care of this in your API. If you don't, you might suffer. Not because of extra calls between client and API, but because if you force doing it on client side, you will force your app to solicit the database server much more than needed. It's one variant of the traditional issue of client vs. server side processing.

But unless you want to write your own general purpose API, I'd strongly advise to design your persistence layer starting from your application architecture, and especially how your architecture will handle the mapping between your objects in memory and the data objects on the database (e.g. object-relational mapping).

The problem has some similarities with serializing/deserializing challenges: i.e. how to map relations between objects, composition of objects, containers of objects, etc... Once you have the clear picture, you should design your database layer with the operation needed to support the primitives of your object mapping.

One good start would be to consider the proxy design pattern for the mapping in-memory proxies that control database objects. The approach I propose you here is very different from developing a universal database wrapper. But it has the advantage of allowing you to get the best from the underlying database architecture, i.e. take benefit of graph and document databases.

  • Thank you for the clarity of thought, this post was very helpful. – Anchor May 22 '16 at 23:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.