5

In our company, we develop information system software in C#.NET, which has client-server-module architecture. Module project does not depend directly on server/client project, but only on shared parts. So the structure of core projects looks like this with their dependencies:

  • server depends on
    • server-shared library
    • common-shared library
  • client depends on
    • client-shared library
    • common-shared library

And module structure looks like this:

  • module depends on
    • server-shared library
    • client-shared library
    • common-shared library

We use Git and dependencies are put in place using submodules. But this leads to "submodule hell" - in a project, which contains only pure client and server, we already have some libraries (common-shared) twice. So developing them is hard, because we can change only one "copy" at a moment and this leads to version incompatibility, when we run client and server together.

Modules make it even more complicated and unusable, because they just multiply libraries copies. When some module is built with bad version of library, it can lead to incompatibility with client and server.

One mentionable solution is to set the projects to depend on the libraries on upper level of directory tree, but that I think is a misconception. It also makes building repositories using CI impossible.

Second solution would be a monorepo, but that throws away a possibility to track issues in our GitLab instance separately, et cetera...

Third solution is maybe using NuGet, but that makes library projects undevelopable (we would have to write some "test project" for developing it separately).

What is the most optimal solution for this codebase?

Thank you very much!

1 Answers1

11

I have always avoided this problem by using NuGet. If you are developing .NET, it's the 'standard' out of the box package manager and works very well.

The downside, if any, is that your dependencies are now binary DLLs rather than source code, which you can debug and edit as you go.

This imposes some restrictions on your day-to-day working practices. You have to open multiple copies of Visual Studio etc. Even though there are many workarounds to common problems which, in my view, can make your life just as easy as having everything in one solution.

However! I would strongly argue that keeping these dependencies separate and only consuming the binary output forces you into better practices.

  1. Unit tests. Because you can't just tweak the code when you are writing the dependent project, you are forced to be sure your library is working correctly in its own project.

  2. Versioning. NuGet libraries need versions. Each library gets its own version which can clearly be seen.

  3. CI. The publish/versioning requirements push you towards automated builds on build servers.

  4. With unit tests and versioning in place, it becomes obvious that the scope of projects should be limited to real functional areas. No more 'common-shared' which gets a new version every hour or so even though only one tiny bit changed.

  5. Finished and complete libraries. With the code split up into scopes and having its own unit tests, you can actually finish bits of it. You're not constantly tweaking a dependency just to make life slightly easier in some app which consumes it, while breaking ten others you didn't know about.

By far, the strongest argument for this approach though is that everyone else is doing it. You don't worry about not being able to debug Newtonsoft.Json or System.Configuration, you just consume the DLL and get on with your life.

You should strive to make your libraries as easy to use and reliable as the published ones you use everyday.

Glorfindel
  • 3,137
  • 6
  • 25
  • 33
Ewan
  • 70,664
  • 5
  • 76
  • 161
  • 1
    I'd add that so long as you have the source code that you can still effectively 'debug' your own DLL dependencies (i.e. stepping through, setting breakpoints, etc.), even under Release configuration. Duly, while you can't make changes, you can still investigate how 2 assemblies are interacting at runtime. – VisualMelon Sep 24 '18 at 12:05