3

We have 2 applications that are part of our product, one is written in native C++ and lives in its own solution, and the other is a C# application in its own solution. Both solutions are in the same repository, and we are planning to introduce a database that both of these solutions will be using.

There is a discussion happening now internally in our software team on whether we should be sharing an enum that will go into the database between the 2 languages by using ifdefs... So the proposal from one side is to do something like this:

#undef ENUMKEYWORD
#undef ENUMNAMESPACE

#if !__cplusplus
#define ENUMKEYWORD public enum class
#define ENUMNAMESPACE EnumShareManaged
#else
#define ENUMKEYWORD enum class
#define ENUMNAMESPACE EnumShare
#endif

namespace ENUMNAMESPACE
{
  ENUMKEYWORD MyEnum
  {
    a = 1,
    b = 2,
    c = 3,
  };
}

I argued that this is a terrible idea with little benefit. My reasoning in no particular order:

  • These are 2 different applications written in 2 different languages
  • Doing this creates a hidden dependency between these solutions and they will lose their "independence"
  • The database should be the final source of truth regarding the up-to-date enum
    • Even if Sqlite doesn't support enums, and we'll have to create many tables for all of our enums, it's not the point
  • Complicates our (already complex) solution and build process even more
  • If other parts of the database change, then we would have to update both solutions anyway.

My proposal is 2 enums, one in C++ and the other in C#, and then writing an integration test to ensure they stay in sync and adding comments to remind developers about the other enum.

Maybe if there was an elegant solution using C++/CLI somehow in the middle, we could try that, but I think creating some enum.cs.h with preprocessor definitions is just ugly and terrible software architecture...

Thoughts?

1201ProgramAlarm
  • 1,529
  • 2
  • 14
  • 21
Shahin Dohan
  • 141
  • 1
  • 7
  • I think a c++/cli solution would require all the c++ projects using the enum to be compiled as c++/cli projects. I would tend to agree that 2 separate enums would be simpler, just make sure to clearly document that changes need to be replicated somewhere else. But I would question why you have so many enums, and if there are other alternatives to consider. – JonasH Dec 14 '21 at 09:35
  • Seems like the proposed solution is to some degree based on my former answer on Stackoverflow [here](https://stackoverflow.com/a/3240772/220984). – Doc Brown Dec 14 '21 at 11:50
  • Seems kind of a lot of effort. Your unit/integration tests should cover off having 2 files to maintain. Your inline comments should refer to the other copy to inform developers there are 2 copies. Worst case have a C# step use reflection to code-generate the C++ .h file. – LoztInSpace Dec 14 '21 at 22:14
  • Not sure if this rises to the level of an answer or not. But instead of horrendous #if statements, why don't you have a simple source of truth in a simple, single text file and then use a transformation process in the build stream of each application that converts the text to a native source code file for that particular application? EG for C# the T4 system. Not sure of the equivalent for c++ though – Peter M Dec 15 '21 at 02:07
  • @JonasH: a C++/CLI program could provide elegant mapping code between a native and a managed enum, still one has to deal with those two enums. – Doc Brown Dec 15 '21 at 13:24

2 Answers2

4

I entered this answer expecting to be strongly disapproving of this approach, but I'm surprised to say that this is not the worst approach, in the right context.
The problem with your question is that you haven't really elaborated on your scenario. There are several possibilities here that would argue pro/con this approach. Without that context, it's impossible to judge if this proposed approach is needlessly complex or whether it is justifiable because of the problem its absence would create.

The overall missing consideration in your explanation is the focus on having a single source of truth.

  • These are 2 different applications written in 2 different languages

Having two different applications doesn't mean that they can't both depend on the same library. There are cases where it makes sense to ensure that two applications always remain consistent about some of their sources.

An example here is when writing a backend and frontend which communicate with each other using models. It makes a lot of sense to put these models in a library of their own so both applications can use it. This ensure that when one of the applications changes the model, those changes also impact the other application.

The models, and in your case the enum, would be a single source of truth; as opposed to having defined the same thing independently twice and being forced to remember to update one when you've updated the other.

If, however, in your case the enums are currently just coincidentally the same and it is possible that they will diverge in the future (i.e. a change in one application doesn't need to be propagated to the other application), then you are correct that immediately defining these enums separately is the right approach.

  • Doing this creates a hidden dependency between these solutions and they will lose their "independence"

Well, yes, but in the scenarios where this approach is a good approach, that dependency would be by design.

"Independence" does not necessarily mean that they have no connection whatsoever. As an example, microservices have an independent lifecycle, as it is the main goal of having microservices; but this doesn't mean that a change in one microservice cannot possibly impact another. Ideally, you'd strive for backwards compatibility so that you are not immediately forced to change, but that doesn't mean that you would never have any reason to want to change it.

whether we should be sharing an enum that will go into the database

  • The database should be the final source of truth regarding the up-to-date enum
    • Even if Sqlite doesn't support enums, and we'll have to create many tables for all of our enums, it's not the point

I would advise against both storing an enum in the database (using it to enforce FK constraints, I assume), and at the same time relying on the enum in your codebase. This would mean you no longer have a single source of truth.

Enum values make sense when dealing with a closed set of values which don't change during your application's (single deploy) lifetime. The benefit of having enums is that you can avoid magic values when performing checks like if(o.Foo == MyEnum.MyValue).

A database "enum" (i.e. a table with simple PK/name fields) makes sense when you're dealing with an open set of values which can be changed during your application's (single deploy) lifetime, e.g. by the end users. However, the consequence of this is that your code can no longer rely on the existence of a specific value (as it can be changed), so you lose out on the ability to specifically reference a given enum in your codebase. A database "enum" is effectively data for the end user (not your code) to make sense of.

It doesn't make sense to do both at the same time, as this would mean that to keep them in sync, you would need to adjust and redeploy your application whenever someone changes the enum.

If your response to that is that there is no logic to ever update the database enum table; then what's the point of having it in the database to begin with? Since there is no way to update the enum (which implicitly also means you're sure there is no outside actor which could be changing these values in the database unbeknownst to you), then your codebase can clearly manage the values using the enum that was defined in the codebase.

If the goal of having the database enum is query optimization, this can be achieved by putting an index on the columns that contain an enum value (without needing an FK to some enum table).

At the very best, the only reason to also add an enum to the database would be if e.g. some external reporting tool needs access to the enum value names to generate its report. But in such a case, the use case for the database enum and the codebase enum is completely independent of one another. Your codebase doesn't use the database enum, and the reporting tool doesn't use the codebase enum.

If other parts of the database change, then we would have to update both solutions anyway.

See the previous point. If the database changes and your code needs to be redeployed because of it; then you're doing something wrong.

Complicates our (already complex) solution and build process even more

To be fair, I don't think I have enough experience on this kind of C++/C# interoperability and what is required to get it working.

I do agree that it takes some effort to write those ifdefs; but I also have to admit that it's a one-off. Once written, any future maintenance on the enum values can be done without even needing to read to adjust those ifdefs.

I also wonder if those ifdefs cannot be simplified using a templating tool that can generate the correct code files for you. It seems the following pseudocode would cover all your enum needs:

if (language is C#)
{
    EnumKeyword = "public " + EnumKeyword;
    EnumNamespace = EnumNamespace + "Managed";
}

If that is correct, then your enum files would only have to specify the C++ keyword and namespace, and the templating tool would be able to generate both a C# and a C++ compatible enum definition. If correct, this further simplifies the enum definition, while at the same time retaining your single source of truth (albeit behind some code generation, which can be automated).


Conclusion

Keeping a single source of truth is a very important tool in your belt as a developer if you intend to lower future maintenance cost and the possibility of introducing bugs by forgetting to keep things in sync.

The proposed approach, while clever, does in fact achieve a single source of truth. That can be a very good thing in the right context; and can justify the existence of that approach.

In that same vein, putting the enum also in the database would undo your single source of truth. Unless there is sufficient justification for it (e.g. that external reporting tool), it should be avoided.

Flater
  • 44,596
  • 8
  • 88
  • 122
2

Whenever heterogeneous applications communicate -- either directly via the network, or indirectly via a database -- non-standardized binary data types are an issue. It's not a question of single source of truth, it's a question of mutual understanding.

Fortunately, in C# and C++ enum types can be mapped to integers. This could be an easy fix: agree on the integers, and make sure that each implementation uses the same numbers with the same meaning. ASCII, ANSI or UNICODE standard committees did nothing else for character encoding. Your team can do so for a couple of enums. And when using a DBMS, subtleties such as binary size of the integer or endianness are not of your concern: the API deals with it.

The usual practice for enums in an RDBMS context is to define a reference table that defines the meaning of each number in plain-text (see here, here, unless you use MySQL's non-portable built in facility). And if the user want another wording, or if you need localization, you just update the table and appreciate how flexible your new approach is.

Last but not least, pre-processor black magic that alters the perception of language constructs is not the best approach. And as soon as you start with a database, it's only a matter of time until you have some VB, Swift and Java applications without possibility of pre-processing, as well as other teams with different case conventions.

Christophe
  • 74,672
  • 10
  • 115
  • 187