Tony Marston's Blog About software development, PHP and OOP

To There and Back - but still in the wrong place

Posted on 8th April 2019 by Tony Marston
Introduction
My Proven Approach
My Database Design
My Criticisms of the article
References
Comments

Introduction

I often browse the internet looking for articles of interest, and as I have been a software developer for 4 decades it is quite common for me to look at articles on that subject, especially those featuring my current language of choice which is PHP. I recently came across an article called Removing Static - There and Back Again which I think is giving misleading information and will cause inexperienced programmers to go down the wrong path when it comes to the design and development of database applications using OOP. Before I criticise this article let me first explain why I think I am justified in doing so.

I used several other languages for 2 decades before I switched to PHP in 2002, so I had a lot of experience under my belt, especially when it came to designing and building database applications. Among the lessons I learned along the way were:

  1. The database should be designed according to the rules of Data Normalisation.
  2. The software structure should follow the database structure.

I have personally witnessed the mess which can be caused by NOT following these lessons, so excuse me if I continue to follow best practices which in my experience have actually been proven to be best.

After having discovered how easy it was with PHP to deal with HTML forms at the front end and an SQL database at the back end I set out to rebuild my development framework in the new language. I had developed my first framework in COBOL in the 1980s which I rebuilt in UNIFACE in the 1990s, so rebuilding it a third time was no big deal. I had built each of these frameworks with the idea that it would take away the need to write all that repetitive boilerplate code and leave me with nothing but the business rules for each application component.

As well as creating this framework to build database applications I have actually eaten my own dog food and used it to build my own ERP application which, as everyone should know, is a collection of integrated database applications each of which deals with a different business domain. I started building this ERP application with the name TRANSIX in 2007, it went live with its first client in 2008, and in 2014 I joined up with Geoprise Technologies who had used my framework for several years and were more than happy to market my application to the entire world under the new name of GM-X.

My decades of experience in designing and developing database applications should therefore qualify me as being somewhat of an expert in this field. The fact that the current generation of new programmers choose to design their software using a methodology which is different from mine does not automatically make their methodology "right" and mine "wrong", it is merely "different". Any area of complexity can be looked at from more than one angle, and it just happens that I prefer to continue using the approach that has served me well for over 20 years. The fact that younger programmers do not have my depth of experience should instantly tell them that they are operating under a disadvantage, but they continue to blindly follow what they have been taught without realising that their lessons have been incomplete, that what they have been taught is not necessarily the "only" way, or the "right" way, it is just one way among several possibilities.

All the programming languages which I used in the 20th century were procedural whereas the language that I chose for the 21st century (PHP) was object oriented. There are far too many OO programmers out there who seem to think that OO programming and procedural programming require totally different thought processes, but I strongly disagree. As far as I am concerned OO programming is exactly the same as procedural programming except for the addition of encapsulation, inheritance and polymorphism (see What is the difference between Procedural and OO programming? for details). This means that I still design each database application in the same old way - database first and software second - but when it comes to writing the code I try to implement encapsulation, inheritance and polymorphism in order to generate more reusable components. Any programmer worth his salt should tell you that the more reusable components you have then the less code you have to write, which then takes less time to test and debug. As time is money the less time which is spent on writing a piece of software the cheaper and more cost-effective it becomes, and being more cost-effective makes the software more marketable and competitive than a rival piece of software which is slow to produce and therefore more expensive.

My Proven Approach

The sample code in the article in question clearly demonstrates that he doesn't understand how database applications work. As I explain in Why I don't do Domain Driven Design there are basically two ways of viewing the problem:

  1. You have an application comprised of a series of unique business rules which incidentally touches the database.
  2. You have an application which touches the database and which incidentally executes some business rules.

By ignoring option (1), which is popular with novice programmers, and instead following option (2) I am able to produce working user transactions using prebuilt templates which provide all the standard boilerplate code and which allow me to add in the business rules later (see The Template Method Pattern as a Framework for details). These templates are built on the fact that when accessing a database table there are only four operations of significance - Create, Read, Update and Delete (CRUD). It does not matter which business entity is being manipulated to carry out which business process, the net result will always be that it ends up performing one or more of those CRUD operations on one or more database tables.

My PHP development framework contains an implementation of the Model-View-Controller design pattern, so each database table has its own Model class. Because every database table, irrespective of its contents, is automatically subject to the same four CRUD operations, I have defined these operations in an abstract table class which is then inherited by every concrete table class. Those business rules which need to be implemented in program code rather than the database design can be inserted into any of the "hook" methods which have been defined in the abstract class.

While the GM-X application currently has over 400 database tables, each with its own Model class, I do not have a separate Controller for each Model. Because each user transaction (or use case) boils down to performing one or more CRUD operations on one or more tables I have created a library of pre-built and reusable Controllers, each of which executes a particular Transaction Pattern, which perform a set of pre-defined operations on an unknown Model (or Models) so all I need to do is supply the name(s) of those Model(s) at runtime using a unique component script. Using this technique I have just 45 different Controllers from which I have built over 3,000 user transactions.

My Database Design

When dealing with currency codes and exchange rates, as shown in Removing Static - There and Back Again, what you are actually dealing with is a Sales Order Processing (SOP) system. This actually requires three separate objects - PRODUCT (what you are selling), CUSTOMER (to whom you are selling) and ORDER (what you actually sell to whom on a particular date). Note that after performing Data Normalisation the number of database tables may actually increase, and in my methodology I treat each table as a separate entity with its own Model class. For example, rather than having a single unit_price on the PRODUCT table I actually have a history of price changes on a separate PRICE_COMPONENT table, each with its own start_date and end_date so that I can maintain different prices on different dates. When performing a lookup on the PRODUCT table I include a lookup on the PRICE_COMPONENT table to retrieve the price which is current at that date.

An ORDER object will also require more than one database table because an order may have any number of order items. This makes "order items" a repeating group which should therefore be split off to a separate table thus giving you an ORDER_HEADER table and an ORDER_ITEM table which exist in a one-to-many relationship where ORDER_HEADER is the parent (one) and ORDER_ITEM is the child (many).

In my first generation of SOP system I dealt with UK organisations which sold only to UK customers, so everything was in UK pounds sterling This meant that there was no need for any currency codes or exchange rates. If any non-UK customer placed an order it was still expressed in UK pounds and the customer was expected to pay in UK pounds.

In the next generation of SOP system the organisation was able to sell to non-UK customers in a currency of their choice. This meant that the order had to maintain two sets of prices - one in the organisation's currency (home currency) and another in the customer's currency (foreign currency). This was achieved quite simply by adding two columns to the ORDER_HEADER table - currency_code and exchange_rate. These new columns were either both blank or both non-blank. All the order values were still held in the database in home currency, but the presence of a currency_code and exchange_rate on the ORDER_HEADER meant that it was easy to switch the displays to show the values in foreign currency simply by multiplying the home currency value by the exchange rate. All online forms had a button which could the amounts displayed between home currency and foreign currency.

Note here an important detail - currency codes and exchange rates have no place on the PRODUCT table as they are only relevant when an order is placed. This is why they have their own columns on the ORDER_HEADER table using values which are obtained from the CURRENCY_CODE and EXCHANGE_RATE tables.

My Criticisms of the article

Now that I have outlined my credentials I will begin to tackle what I think is wrong with that article.

  1. While having a column called price on the PRODUCT table may seem a good idea to a novice developer, it prevents the ability to maintain different prices on different dates. Anybody who has ever had to deal with a price change should know what I mean. The ability to say "on so-and-so date change the price for product X to £Y" is the proper way.
  2. The idea of having a separate class property for each column in the table may seem like a good idea to a novice programmer, or one who has been taught OO theory but not database theory, but to an experienced database programmer this is a retrograde step. Relational databases deal with sets of data, not individual columns, which is why when you read a record from the database the result set contains an associative array of scalar values instead of an object with a bunch of properties. An observant programmer should also have noticed that when the SUBMIT button is pressed on an HTML form what is sent to the PHP script is an array of scalar values. I see no need to deconstruct each of these arrays into its individual elements so that I can send them one at a time into the object when I can pass the entire array around as a single argument. Some OO programmers may recoil in horror at this idea, but believe it or not this is a prime example of loose coupling which is considered by all competent programmers as being a "good idea" as it is better than tight coupling.
  3. The idea of having to specify a product's price in the class constructor is also wrong. It may be an OO programmer's dream, but to a database programmer it is simply not how it is done. A product's price is simple a column in a database table which can be updated at any time and can be displayed along with all the other data belonging to that product. When it is necessary to add the current price to an ORDER_ITEM you simply instantiate an empty Product object before using the relevant operation to retrieve the selected product's data from the database. It should be obvious that in this very common scenario it is not possible to feed the price into the object when it is constructed as that value is not known until AFTER you have told the object to read from the database.
  4. The idea of a separate object for Price is totally wrong. Price is a property of PRODUCT and as such it is passed around as a simple scalar value and not an object. It is a column in the database, and only tables are entitled to have their own classes and objects, not columns.
  5. The idea of setting the currency code when you retrieve a product's price is totally wrong. Prices are always expressed in the organisation's home currency, and a currency code only becomes relevant when creating a sales order in a different currency. This means that the currency_code column on the ORDER_HEADER can either be set to blank or non-blank as required. If it is non-blank then the software should automatically retrieve the relevant exchange_rate which is appropriate for that order date.
  6. The idea of using a static method to set the currency code is also wrong. Currency code is nothing more than a column in the ORDER_HEADER table which is populated from a field in the HTML form when the ORDER_HEADER record is created.
  7. The idea of converting a product's price into a foreign currency amount when you retrieve the product's data is totally wrong. It is ALWAYS retrieved in home currency, ALWAYS stored in home currency on the ORDER_ITEM table, and only converted into foreign currency when required using a simple calculation which uses the exchange_rate which was stored previously on the ORDER_HEADER record.
  8. The idea of using a service to provide currency calculations is making a mountain out of a mole hill. The value in home currency is always known as it the value which is held in the database, and the value in foreign currency, when required, can be obtained with a simple one line calculation, as in:
    $display_value = $home_currency_value * $exchange_rate;
    
  9. The idea of having a separate Controller for each Model is wrong as it immediately cuts off one of the most powerful features of OO programming which is polymorphism. Having a Controller which is tied to a single Model, and a Model which is tied to a single Controller, is a perfect example of tight coupling which every competent programmer knows should be avoided. As I have stated previously the only operations which are relevant to a database table are Create, Read, Update and Delete, you should then build these operations into every table class. If you know what you are doing you should immediately see the benefit of defining these operations in an abstract class which can then be inherited by every table class, thus cutting out the need to write a lot of repetitious boilerplate code at a single stroke. Then instead of writing a Controller which calls these methods on a specific Model class you can write a sharable Controller which performs these operations on an unspecified Model class and only supply the class name at runtime. This is called polymorphism, and in my implementation any one of my 45 sharable Controllers can be used with any one of my 400 Model classes. That produces an enormous amount of reusability which I have never seen available in any other framework. By "enormous amount" I mean 45 x 400 = 18,000 (yes EIGHTEEN THOUSAND!) opportunities for polymorphism.

There are other statements in his article which I also find to be very questionable. For example:

An entity with the same ID can return different values on different calls of the same method. It's like my name would be "Tom" in the morning and "John" in the afternoon.

An object which represents a database table does not have an ID other than the primary key of the record which it contains. The value in the name property for that object would therefore be the value in the name column retrieved from the database using whatever arguments were passed in the ->read() method. The value would only change if someone updated it.

Due to a static design of CurrencyProvider, we cannot set currency at the single place of application.

You should never want to set the currency code in a single place in the application. You only set the currency code when you create a sales order using a value which is supplied by the user in the HTML form, so different orders can have different currency codes.

How do I show a price for all the currencies we support?

You don't. The product's price is always that in the price file which is expressed in home currency. A value in a different currency is not relevant until you add that product to a foreign currency order, in which case you can then use the exchange rate for that order date to convert the value.

The idea that you can refactor a code base in one big step using an automated tool is nothing but pie in the sky - it can't be done. A tool can only look at small pieces of code in isolation and suggest possible alternatives. It cannot say that the whole design is wrong, the classes are wrong, the methods are wrong, that the use of encapsulation, inheritance and polymorphism is not as good as it should be. That takes a human eye backed up with human intelligence, and regardless of what anyone says about Artificial Intelligence (AI) computers will be nothing more than fast idiots that can only obey simple commands for many years to come.

There is only one point in that article with which I agree - getting rid of static methods. The use of such methods to set and get the currency code is obviously the brainchild of someone who is trying to prove how clever he is with OO theory, but would never be done by someone who knows how database work and who knows how to write code to put data into and get data out of a database.

When you consider that OO programming means "writing code that is oriented around objects" you should instantly see that using static methods does not fit this description as you never actually instantiate the class into an object before you call one of its methods. If there is no "object" then how can it be "oriented around objects"?

Here endeth the lesson. Don't applaud, just throw money.

References

Here are some heretical articles I have written on the topic of OOP:


counter