In our API we've got a few central datatypes which need to be "decorated" (so to speak) after retrieval from the database with calculated values. The database is accessed through an ORM which follows a Table/Entity dynamic heavily inspired by the CakePHP 3 database layer, where a Table object is used as an intermediary between the database and the application that takes in and hands out rows as model object instances. So instead of just retrieving data from the database and returning those rows we need to preprocess the returned data before it's actually usable. Here's a few use cases that have come up to better explain what I mean:
- Objects have numeric values which are translated to user-friendly labels (normally this is logic which I'd keep purely on the client, but for business security reasons some of this data needs to be kept on the server only, admittedly a bit of an edge case)
- Objects need to have an associated rating value which is pulled from the most recently added rating
- Based on a combination of calculated values such as this and stored values, a complex schedule object is constructed
On their own, any of these individually are actually pretty easy to do with a simple map()
operation over the returned result set. The same thing applies for when you want multiple calculated values, you can just do more map operations to calculate and add those fields as necessary.
That said, this approach has two major drawbacks:
- It means that you need to do an additional step of postprocessing everywhere you want to work with these calculated values, which isn't particularly DRY
- Some of these transformations depend on other transformations being done first, otherwise they just don't have the data available to work with
To handle both, I've been thinking that the best approach would be to move this code into the ORM, then modify the ORM so that the interface will (externally) allow access to the calculated virtual fields the same way it deals with database columns. Internally it could then map these virtual fields to transformation functions, and internally determine any potential needed dependency transformations to solve the second issue.
(As an aside, I'm wondering if this also removes the need for the returned rows to be actual objects as opposed to simple hashes. Right now each row instantiates a new object with the field data set on it, but if all calculation or modification of the data is moved out of the model then the object just becomes a bag of properties--a hashmap, essentially, with no internal logic of its own. Which may not actually be a bad thing I think)