A short while ago I came across a blog post called Write Only Business Logic: Eliminate Boilerplate. While the author's objective in trying to eliminate repetitive boilerplate code is admirable, I'm afraid his approach is no match for the results which I achieved 20 years ago.
The opening paragraphs in his post go as follows:
When did we accept as normal that 60-70% of code we write is orchestration and boilerplate? Leaving just a fraction of our codebase dedicated to actually addressing real business challenges.Multiple Controllers that do transformations and delegation, Application Services that are only there to execute method on object and then save it. Endless boilerplate that obscures the real business logic buried somewhere in the middle.
What if you could skip all that technical noise and write only the code that matters to your business?
The ability to write only the code that matters to your business
is achieved in my RADICORE framework by judicious use of the Template Method Pattern where all boilerplate code is provided by the framework and all "real" business logic is inserted into any of the numerous "hook" methods which can be overridden in any of the table subclasses.
Nice try, but no cigar. You are on the right track, but you are still a long way from what I achieved 20 years ago using PHP 4.
Since a major motivation for object-oriented programming is software reuse, it is important to develop classes which are reusable. This can be done by looking for patterns in your code, where something repeats. In some cases where an entire block of code is duplicated you can place that code in its own module so that it can be called multiple times instead of being repeated multiple times. In other cases there are similarities which are interspersed with differences, so what is required is the ability to separate them so that the similarities can be placed in reusable modules and the differences can be placed in unique modules. This technique is known as programming-by-difference and was described in a paper called Designing Reusable Classes which was published in 1988 by Ralph E. Johnson & Brian Foote. In it they state that when a group of classes share the same protocols (operations or methods) then those protocols can be moved to an abstract class which can then be shared via inheritance by any number of concrete subclasses.
I programmed in several non-OO languages before I switched to PHP early this century, and one valuable lesson I learned was then when building a database application you are writing software which communicates with entities in a database, not entities in the real world, and those entities are called "tables". It was also obvious to me that regardless of the table's contents, each one was subject to exactly the same operations - Create, Read, Update and Delete. Every database application is comprised of a number - sometimes hundreds or even thousands - of what I call user transactions (use cases), and every one, regardless of how simple or complicated it is, follows the same pattern in that it performs one or more CRUD operations on one or more tables.
I also learned the benefit of designing a properly normalised database first, then building the software to work with that design. The idea of designing the software first, then building the database to work with software is something that I would never do. The database is the centre while it is the code which is a minor implementation detail.
I also learned the benefits of using the 3 Tier Architecture with its Presentation, Business and Data Access layers, which is why I used it as the starting point for my PHP code base. I quickly noticed that the code which dealt with the HTTP request was totally divorced from that which produced the response, which is why I split my Presentation layer into two, thus giving me the following four components in every application:
Notice that this not contain any of those application services, command/query handlers, routing logic and transformations between layers as dictated by what you call a "traditional architecture". Nor does it have a separate Controller for each Model as that produces nothing but tight coupling which is supposed to be avoided.
When I taught myself OOP by studying the PHP manual I was totally unaware that I was violating the "rules" of OOP, OOD, DDD et cetera. When I later examined these rules I quickly realised that they were written by theoreticians who did not understand how databases worked, so I was doing the right thing by ignoring them as the results which I achieved were far superior. By "results" I mean the volume of reusable code which I produced. The more reusable code I have at my disposal then the less code I have to write to get the job done, and the less code I have to write then the quicker I can get the job done. After creating a database table I can create a basic set of transactions, minus any custom business rules, to view and maintain the contents of that table in a matter of minutes without having to write any code - no PHP, no HTML, no SQL. My levels of productivity would be difficult to match with any other framework.
When I began building the prototype from which my framework evolved I made some simple decisions which, when combined, provided enormous benefits. Among these decisions were:
load()
, validate()
and store()
- which I saw in quite a few examples of other people's code - I placed these function calls into a wrapper function. This replaced several calls to low level functions with a single call to a wrapper function. This has enabled me to make enhancements to the wrapper functions without having to change those places where the wrapper is called.InsertProduct()
, InsertPerson()
and InsertOrder()
, which would result in code which was tightly coupled to those entities, I decided to use more generic names such as insertRecord()
, updateRecord()
and deleteRecord()
. I can use those methods on any entity, yet the meaning will still be crystal clear.Here is an example of some early wrapper functions:
External call | Internal implementation |
---|---|
$object->insertRecord($_POST) |
$fieldarray = $this->validateInsert($fieldarray);
if (empty($this->errors) {
$fieldarray = $this->dml_insertRecord($fieldarray);
}
return $fieldarray;
|
$object->updateRecord($_POST) | $fieldarray = $this->validateUpdate($fieldarray); if (empty($this->errors) { $fieldarray = $this->dml_updateRecord($fieldarray); } return $fieldarray; |
Notice that the load()
function is carried out by a single argument on the method call. I used this code to build my first table (Model) class, along with 5 Controllers to perform the List, Insert, Enquire, Update and Delete operations using that class. Here is an example of the Controller which performed an INSERT operation:
<?php require "classes.person.class.inc"; $object = new person; $fieldarray = $object->insertRecord($_POST); if (empty($object->errors)) { $result = $object->commit(); } else { $result = $object->rollback(); } // if ?>
I followed this by duplicating all six scripts to work on a second database table. Then I moved all the duplicated code to an abstract class and made the two concrete classes inherit from it. This emptied out all the methods, but left the constructor to load in the meta-data. In order to avoid duplicating each of the Controllers I split then into two - a unique component script which calls a reusable Controller, as shown below:
-- a COMPONENT script <?php $table_id = "foobar"; // identify the Model $screen = 'foobar.detail.screen.inc'; // identify the View (a file identifying the XSL stylesheet) require 'std.add1.inc'; // activate the Controller ?> -- a CONTROLLER script (std.add1.inc) <?php require "classes/$table_id.class.inc"; $object = new $table_id; $fieldarray = $object->insertRecord($_POST); if (empty($object->errors)) { $result = $object->commit(); } else { $result = $object->rollback(); } // if ?>
I eventually encountered some processing rules which couldn't be handled by boilerplate code in the framework, so I modified the abstract class to include calls to empty hook methods at various points in the processing flow. The amended wrapper functions now looks something like the following:
External call | Internal implementation |
---|---|
$object->insertRecord($_POST) | $fieldarray = $this->pre_insertRecord($fieldarray); if (empty($this->errors) { $fieldarray = $this->validateInsert($fieldarray); } if (empty($this->errors) { $fieldarray = $this->commonValidation($fieldarray); } if (empty($this->errors) { $fieldarray = $this->dml_insertRecord($fieldarray); $fieldarray = $this->post_insertRecord($fieldarray); } return $fieldarray; |
The hook methods are designed to do nothing unless they are overridden in a concrete subclass. Note that they are not abstract methods. These methods look like the following:
function _cm_whatever ($fieldarray) // interrupt standard processing with custom code // if anything is placed in $this->errors the operation will be terminated. { // customisable code goes here return $fieldarray; } // _cm_whatever
The developer simply has to write code and insert it into the relevant hook method. He does not have to write code to call this method as that is done by the framework. This demonstrates how to use the Template Method Pattern which is an essential pattern in any framework. It is an example of the Hollywood Principle (don't call us, we'll call you).
To create all the HTML pages I use XSL stylesheets. Although I started off with a separate stylesheet for each page, after a lot of refactoring I managed to reduce this to a library of just 12 reusable stylesheets which can create any page I need in my application.
I single-handedly created the RADICORE framework for building database applications, and I have used this framework to build, again single-handedly, an ERP application which has now grown into a product called GM-X which has been used by multi-national corporations on several continents. This is comprised of the following:
The framework contains the following reusable components:
The only code which a developer has to write to cover the unique business rules is inserted into the relevant concrete subclass using any of the predefined hook methods.
Until you can achieve comparable levels of reusability I'm afraid that, in my book, you still have a lot to learn.
Here endeth the lesson. Don't applaud, just throw money.