I am looking for some advice about structuring Delphi programs for maintainability. I've come to Delphi programming after a couple of decades of mostly C/C++ though I first learned to program with Turbo Pascal, so I'm not uncomfortable with the basic language. In my previous experience with C++ and C#, I became a TDD convert through using cxxtest and NUnit.
I have inherited this program that I am now responsible for maintaining. It consists mainly of forms, and a couple of data modules. The application business logic and data access is mainly scattered about the forms, and the data modules are mostly just places for global ADO objects to live. The database access is generally done by referring to a global instance of TADOQuery or TADOCommand, formatting SQL text right into the relevant property of the object, and calling its Open or Execute method.
I am trying to get the business logic into a degree of encapsulation where it can be unit-tested. I've seen this answer and it makes perfect sense as far as abstracting logic from forms. I am wondering what the best practices are for data access. My thinking is that the data modules should expose a sort of app-specific mini-API (probably with all virtual methods) so that they can be replaced with mock objects for testing. The link at this other answer shows some examples that lead me to believe I'm on the right track, but I'm still interested in seeing some kind of best practices document about data modules. Most of the pages that I can find through Google present the same kind of examples about all the cool stuff you can do at design time with hooking up data-bound controls to queries and that sort of thing, which I'm not very interested in at the moment.
Personally I'm not a fan of TDataModule. It does very little to encourage good OO design principles. If all it was used for was a convenient container for DB components that would be one thing but far too often it becomes a dumping ground for business logic that would be better off in a domain layer. When this happens it winds up becoming a god class and a dependency magnet.
Add to this a bug (or maybe its a feature) that's continued to exist since at least Delphi 2 that causes a form's data aware controls to lose their data sources if those data sources are located in a unit that isn't opened before the form.
My suggestion
If you're not familiar with it the technique is referred to as domain driven design. Its certainly not the only solution but its a good one. The basic premise is that the UI, business logic and database change at different rates and for different reasons. So make the business logic a model of the problem domain and keep it separated from the UI and database.
How does this make my code more testable?
By moving the business logic to its own layer you can test it without interference from from either the UI or the database. This doesn't mean your code will be inherently testable simply because you put it in its own layer. Making legacy code testable is a difficult task. Most legacy code is tightly coupled so you will spend a good deal of time pulling it apart into classes with clearly defined responsibilities.
Is this the Delphi style?
That depends on your perspective. Traditionally, most Delphi applications were created by developing the UI and the database in tandem. Drop a few db aware controls on the form designer. Add/update a table with fields to store the control's data. Sprinkle with a liberal amount of business logic using event handlers. Viola! You just baked an application. For very small applications this is a great time saver. But lets not kid ourselves, small applications tend to turn into big ones and this design becomes an unsustainable maintenance nightmare.
This really isn't the fault of the language. You find the same quick/dirty/shortsighted designs from hundreds of VB, C# and Java shops. These kinds of applications are the result of novice developers that don't know any better (and experienced developers that should know better), an IDE that makes it so easy to do and pressure to get the job done quickly.
There are those in the Delphi community (as there are in other communities) that have been advocating better design techniques for a long time.