Fluid designs or functionality oriented designs
“Functionality” is the implementation of a given requirement as a series of functional steps that a user or the system (referred as actors) must do in order to achieve the result described by the requirement. For eg., the requirement can be defined as “Allow the planner to create shipments”. In one possible design, the following functional steps may need to be done by one or more actors:
- “Define rules to associate orders with planner, by the organization administrator”
- “Setup a set of rules to consolidate orders into a shipment, by the planner”
- “Add orders to the user’s workspace based on organization rules automatically, by the system”
- “Run the rules setup by the planner to create shipment, by the system”
This set of actions done by various actors together is called a “functionality” of the product. The typical steps to design the above functionality, starts from the data and the database. New database tables are identified such as “Rules” table associated to orders and the planner workspace, to store the rules that are setup, the “workspace” table, the “shipment” table to store the shipment and the related tables to shipment and so on. These are then normalised appropriately. The columns required for database tables shared across functionalities, for eg., in the above Organisation, User and Order are identified, consolidated and modified to store new data. The associations between new tables and existing tables are identified, for eg., in the above Rules table to be associated with the User table, are identified and modified. Once done, the next steps are to code the DOs and the DTOs or the DAOs to perform CRUD on these tables. Then to write the “processor beans” that tie all these DTOs/DAOs/DOs together to bring all the CRUD operations into a logical implementation based on the functional design. Later or maybe in parallel the user interactions are considered and designed. Sometimes, user interaction is just considered as a translation of the data in the database tables to be rendered directly on UI screens. I call such a design as “the database oriented design”. As everything else, such a design process has its own disadvantages. The underlying disadvantage that rears its head in various forms in such a design is that “the mind is oriented towards visualising a design in technical dimensions as opposed to the functional dimensions”. Looking at some of the disadvantages:
Typically, in almost every such design, the “user” is exposed to the limitations in the designs of the database tables. While it is definitely possible to combine the data in the tables in various manner to decouple the user from the table design, we find that our thoughts are restricted by the data split that we designed. So, the orientation of all screens or the APIs for interaction with other systems, typically tend towards emulating the same split. Database tables can be designed in any manner starting from highly normalised, highly restrictive, highly validated non-flexible relational data design on one end of the spectrum to a highly flexible, denormalised, non-restrictive, non-related data stored in noSQL databases on the other end of the spectrum. What is important to note is that irrespective of how the data is stored, database designs are only a technical implementation to achieve an incidental requirement of all applications i.e., to have persisted business data available to work with across functionalities or business transactions. It should be remembered that the business transactions are the core of the product, not database tables.
The impact of the core disadvantage is subtly seen in modularisation and never recognised as a disadvantage. Modularisation is a very common terminology loosely used by everyone in the industry, yet, the implications of the technical oriented mindset on modularisation is not understood clearly. The impact of this disadvantage is succinctly bought out in the below diagram (usually used to explain MVP).
The first diagram series brings out the impact of focussing purely on technical modularisation. Doing a database oriented design takes away the “functionality” from the design. By taking this away, it allows designers to think irrationally looking at the product as just a mathematical probability or permutations and combinations and hence being able to split functionality in any and every dimension.
Taking the create shipment eg., again. Rules need to be setup that need to determine whether an order belongs to a user’s workspace or not, and rules need to be defined that need to determine whether an order can be consolidated with another other or not to create a shipment. It should be noted here that while both these are considered to be rules, functionally they are totally different. In the first case, the rules are purely expressions run on the attributes associated with the Order. So, it can say order.origin.region is region 1 and order.destination.region is region 2, associate with planner 1 or it can say if the orderline.product is product 1, then associate it with planner 1 and so on. But, in the second case, what is required is a series of “if-else” rules that need to be evaluated and a logic run before consolidating or splitting. So, it is possible that a set of rules goes in this manner, if order travels from the same origin 1 to same destination 1, then consolidate if the quantity of the order does not exceed an available container size in that route. This is not an expression evaluator on an object, but a set if-else rules engine that needs to apply on various objects such as the order, route, container etc.
When a database oriented design is done, given that the mind is oriented towards technical grouping, we tend to say that Rules is a common object, the rules framework works with it, hence a single “Rules framework” is what is needed for both the scenarios. Thus, we end up with a scenarios where either the first scenario has to uptake an unnecessary version of the code for the second scenario or the second scenario has to be limited by the limitations of the first scenario. We are limited by our own self-created limitations of code reuse, modular coding, no repeated coding because it is a maintenance nightmare and so on. While boundaries are good to have, it is time to ask ourselves what are we sacrificing in order to maintain these boundaries and have these boundaries become unnecessarily limiting for correct modularisation? Most other limitations such as how do I scale only one functionality as opposed to scaling all functionalities, a set of embedded “If” statements to define features and availability of features based on license and so on get introduced the minute we talk about technical database oriented designs.
Fluid Designs
The second diagram series in the above diagram brings out the concept of fluid designs. Fluid designs are functionality oriented designs. Here, the design starts with identifying pieces of features that form a given functionality. Next using these split features, the single core feature is identified by stripping or stubbing features that can be considered as enhancements to the core feature. This gives us a skeleton feature to start the implementation. Using this skeleton, we recognise the actor interactions, business objects and transactions that has to be executed on that business object for the actor interactions. To continue, we pick the next feature and strip it down till the next coherent feature with the current implementation is available and design and implement this and this goes on.
It should be noted that such a design is a change in the perspective as opposed to completely removing the technical design elements from the design. While designing in this manner, it is assumed that the database frameworks, logging frameworks, integration framework, authentication frameworks etc., related technical frameworks are incidental and already available with a concrete defined methods to use them, as a part of some underlying platform. This anyway is the case for most existing products. If it is a new product, these frameworks are typically available as libraries that can be picked up and used instead of reinventing the wheel.
The point to note here is that the designer is forced to focus on the functional aspect along with the technical aspect. The design is looked upon as actor interactions, business objects and business transactions that are recursively improved as the implementation progresses. So, at any given point in time, a working version of the business transaction is present as opposed to just any working code.
To look at the eg., from the above create shipment, the steps to designing goes as below. First is to identify the features. The possible features identified are:
- Workspace creation by planner
- Orders added to a workspace
- Create shipment from a set of orders belonging to a workspace
- Rules based addition of orders to workspace
- Rules based creation of shipments
Next is to identify the core transaction from these features. It can be identified as create workspace, add orders, create shipments from orders in workspace. All other features are just add ons to this core feature. We next identify how we want to implement the actor interactions to get this core transaction working. One possible sequence is, step1: user creates workspace specifying a name, step2: as orders come in, we add all orders to all the workspaces created by any user, step3: user selects orders and manually clicks create shipment button to create the shipment. step4: shipment is created and Order changes status to planned and is removed from all the workspaces it is present in. So, this becomes the stripped down, stubbed version that is the first implementation.
Now, we identify the business objects for the core transaction. Possible objects are the Workspace, Orders and Shipments. Orders should already be present in the system and the only possible addition that we will have to add to Order is “belongs to workspace” attribute. It should be noted, that we identify the business objects and their attributes and not the database tables. The database tables creation from these business objects needs to be pushed into the database storage layer of the platform. The simplest implementation could have created a single table for each business POJO object created. A more complex version can split the object into multiple tables based on data size and data relations. Even more simpler implementation can store these objects are pure serialised blobs and retrieve them to construct the object as needed. But, to reiterate, data storage is incidental and needs to be handled in that manner.
Once we have identified the objects, we identify the actions that need to occur on these objects based on our actor interactions that we have defined. The Workspace has to be created, Orders need to be added to the workspaces and Shipments need to be created from the orders in workspace. We define APIs for each of these recognised actions. How, these APIs are exposed is the responsibility of the integration framework and should be left as that, possibly the layer exposes the API as JSON. Then we identify the logic to execute and when these APIs need to be triggered. This leads us to design and write two screens first to create workspace and the second to show the orders in workspace and create shipments from the selected orders, write two business objects, modify one business object, define three APIs and logic for them and one API modification for adding order to the workspace when they arrive into the system.
The point here is that as we implement these features, we are working with functionally complete versions of the transaction that can be demo’ed. The modularisation is automatic based on the functionality and features, the core features come into one module and as features are added they can be split off into other modules or added to the same module based on analysis. Scaling, logging, debugging everything becomes functionality oriented and hence relates easily back the business transaction that is being executed.
The fluidness in the design becomes apparent when we start extending the functionality with features. Let’s look at the rules related feature addition. The setup of rules to add Orders to the workspace. We start stripping this feature down to the very minimum implementation and we find that we ask the right questions here such as “What are the actual fields that make a difference in adding orders to the workspace?”. Possible that we identify that that typical planners work with orders traveling from one region to another and nothing more. So, we just implement region based rule for addition of order. This allows us to add functions to the Location business object to classify them into regions and using these regions to specifically add orders to the workspace. Here, we implement a very specific logic without any rules framework involved. The other functionalities get automatically extended based on functional analysis as opposed to technical analysis.
When starting from a rules framework related design, we tend to say “Why restrict the user, they can use any field in the order to evaluate an expression to add an Order to the workspace”. While technically this seems easy to implement and handles all cases that a user would need, this is the typical mathematical permutation and combination implementation which increases complexity of the product unnecessarily without providing any extra valuable product features to the customer. Also, we have introduced an unused complex rules framework code that is not even necessary for the functionality.
Fluid designs are malleable designs. It is definitely possible that fluid design introduces duplicate code, possible introduces duplicate bugs. But, these are solvable problems which can be solved with a correct platform definition and choice. The technical details can be pushed to the platforms and feature implementations can be reduced to correct designing as opposed to extensive coding and testing.
Published on Java Code Geeks with permission by Raji Sankar, partner at our JCG program. See the original article here: Fluid designs or functionality oriented designs Opinions expressed by Java Code Geeks contributors are their own. |