An Assembly History of NDbUnit
As some of you are aware, I have some time ago assumed the role of primary committer on the opensource NDbUnit project. The history of this project is that it began with just two database ‘target’ types supported: Microsoft SQL Server and Microsoft OLEDB-supported databases.
All of this capability was wrapped up into the single assembly, NDbUnit.Core.dll that had hard binary dependencies only on standard Base Class Library assemblies that shipped with the .NET framework (System.Data.dll that contains the ADO.NET client implementations for SQLServer and OLEDB in this case). Since a developer had to have the .NET Framework installed to be writing .NET software in the first place, this effectively meant that NDbUnit.Core.dll had a hard dependency only on other binaries that all adopters would already have on their computers.
Essentially it meant that all developers using NDbUnit would use the same assembly references regardless of what database ‘target’ they were working with.
Growing to Support Third-Party (non-MS) Databases
Due largely to the contributions of others, the NDbUnit project slowly grew to work with and support additional, non-Microsoft database targets. Today, in addition to the support for MSSQLServer and MSOLEDB, NDbUnit also supports the following third-party databases:
- SQL Server CE
I include ‘SQL Server CE’ here even though its (obviously) also a Microsoft database because it turns out that if you install Visual Studio and fail to check the box to include ‘mobile development support’, you don’t get the needed System.Data.SqlServerCe.dll assembly installed on your PC.
The trick will all of these third-party database targets is that they all require some third-party assembly in order for ADO.NET to interact with them. And since NDbUnit is leveraging ADO.NET under the covers to interact with your database, NDbUnit needs these assemblies in order to work properly…
- MySQL (MySql.Data.dll)
- SQLite (System.Data.SQLite.dll)
- SQL Server CE (System.Data.SqlServerCe.dll)
The Trouble Begins…
The real issue at the core — no pun intended — of this trouble is that as originally conceived (and thus as was the case when I assumed ‘ownership’ of the codebase), all of the supported database targets were compiled into the single NDbUnit.Core.dll assembly. When the only supported databases were SQL Server and OLEDB, this didn’t matter too much. But as the number of supported databases grew and expanded to include non-Microsoft targets, the fact that all of this code was compiled into the single NDbUnit.Core.dll assembly meant that anyone wanting to use NDbUnit for one database target was required to take a dependency on all the database targets. This results in the ungainly dependency graph shown here…
…and it effectively means that if you reference in the NDbUnit.Core.dll assembly, you also have to reference in all of the other third-party assemblies whether you want / need to use them or not.
Clearly not a very good ‘take just what you need’ story for NDbUnit. Instead, the story became ‘all or nothing’ and for a project with a wide-variety of adopters intended to be useful in a wide variety of situations, clearly this was becoming a real problem.
And worse, its a problem that will expand as other database targets are added to the project (e.g., Oracle support is under construction right now) — any new database target support assemblies would need to be referenced by any and all NDbUnit users right along with NDbUnit.Core.dll.
…and Leads to a Fix!
The solution I have now introduced into the NDbUnit project (which frankly isn’t really all that ground-breaking) is to divide the dependencies into more database-specific assemblies.
Where previously all of the support for each different database target was incorporated directly into the single, monolithic NDbUnit.Core.dll assembly, now the core assembly merely contains the common interfaces and base classes that each of the database target-implementation classes depend upon. The database-target-specific code has been moved into separate assemblies, one each for each of the supported database targets as follows:
|Database Target||NDbUnit Client Assembly|
|MS SQL Server||NDbUnit.SqlClient.dll|
|MS SQL Server CE||NDbUnit.SqlServerCe.dll|
Each of these NDbUnit client assemblies in turn depends on NDbUnit.Core.dll and exactly ONE (and only ONE!) of the other ADO.NET-supporting assemblies.
As an example, the following dependency diagram shows the assemblies needed for support of MySQL using NDbUnit. Your test project needs only to reference the MySQL-specific NDbUnit client assembly (NDbUnit.MySql.dll), the common assembly NDbUnit.Core.dll, and the MySQL-provided ADO.NET support assembly, MySQL.Data.dll.
While the effect of this is that the NDbUnit-adopter has more separate assembly dependencies to manage when setting up their test projects, the benefit of this approach is that adopters of NDbUnit are now only required to take a binary dependency on the libraries that are needed to support just the database target that they are interested in working with rather than a dependency on all of the assemblies needed to support every conceivable database target that NDbUnit supports now and for the future.
Please also note that there is NO change in any of the namespaces with any of the types in NDbUnit that are being introduced with this refactoring, so all that is needed to update any of your projects to use this new assembly distribution model is to download the latest binaries (or get the code and build it for yourself), change your project’s references to whatever is appropriate for your database target, and recompile. Your tests should still ‘just run’ as before.
Another Positive Side-Effect
One other side-benefit of this change in approach is that it now permits the versioning of the assemblies completely separate from each other. A change in the NDbUnit MySQL client assembly won’t result in a (somewhat needless) version change for anyone NOT using MySQL (for example) and users can only bother to update their versions when changes are introduced in the libraries that support their specific database targets.
This will also help when introducing support for multiple DB target versions from the same vendor (e.g., Oracle 8, 9, 10, 11, whatever) in that each client support assembly can be considered a separate assembly and chosen by users as needed for their version of the database target.
Happy database testing~!