Enforcing a nice 3-tier architecture
One of the most know architectural patterns is the 3-tier separation of applications into:
- GUI layer: Contains the code for user interactions
- Application/Server layer: Server side logic
- Data layer: The data storage
The idea here is the reduce the complexity of the complete system by partitioning the code into 3 separate sub system. The interfaces between the component should be as clean as possible with a loose directional coupling GUI -> Server -> Database.
The reason for bringing this up is that I have run into difficulties fixed the JobDAOTester#testGetStatusInfo test, because of a creep of gui related logic into the data layer code. The problem is located in the HarvestStatusQuery constructor:
... public HarvestStatusQuery(ServletRequest req) { String[] statuses = (String[]) UI_FIELD.JOB_STATUS.getValues(req); for (String s : statuses) { if (JOBSTATUS_ALL.equals(s)) { this.jobStatuses.clear(); break; } this.jobStatuses.add(JobStatus.parse(s)); } ...
First of all, the data - server layer decoupling is broken because this data layer specific method contains a server type argument. This means the data layer has knowledge of the server implmentation (it is a web service).
But even worse is that the GUI logic is exposed in this code. Both by the type of data used (f.ex. UI_FIELD), but also in the methods from these classes which contains functionality specific for the gui. In case of the UI_FIELD.JOB_STATUS.getValues(ServletRequest)
public String[] getValues(ServletRequest req) { String[] values = req.getParameterValues(name()); if (values == null || values.length == 0) { return new String[] { this.defaultValue }; } return values; }
where undefined values are assumed to mean some kind of default values should be used. This is properly driven by some GUI intrinsic behavior, but appears confusing seen from a purely data model point of view.
The fundamental problem here is that you can not work with the data layer code without understand both the server and GUI usage of this functionality. This makes is much more difficult to understand the full context of this code and makes the code very brittle.
The reason for the added problem with exposing gui related functionality, is that it very difficult to validate gui code with automatic testing.This is one of the motivations for the many GUI centric design patterns like the MVC pattern, which are attempts to isolate the presentation specific code even further compare to a simple 3-tier architecture.
An example of the dangers of working with such closely couple code would be the changing of the HarvestStatusQuery constructor to better reflect only data layer concerns. This will certainly break the application in some way, but the precise implications are difficult to isolated and the automatic regression test will not be very helpful, because is properly doesn't affect the GUI code.