Question

A Quick Note

I know the question title is a little odd but I haven't had much caffeine this morning so I'm having trouble thinking clearly right now. If you have any suggestions for a better title, please comment below and I'll update.


The Problem

I've built a custom logging solution that allows us (my organization) to log events within our applications. The solution requires an event type when logging an event and I'd like to add support for application specific event types.

We currently have a single enum that contains all of our event types, and I believe this is not the correct way to approach it since adding specific event types related to each application will increase the number of values available in the enum by a lot. For example, with support added for only two applications, we're already up to 22 different event types.

My Current Thoughts

I've thought about creating a single class that contains multiple enum declarations for the different event types, but this will not work since I can't create a single method that will handle all the different enums, and I do not want to create multiple methods for each different enum since that will be a maintenance nightmare.

// Simple example.
public bool LogEvent(EventType type) { /* ... */ }

A possible solution would be to create a collection of classes with several const declarations like so:

public sealed class EventTypes {
    public class EventType {
        public string Name { get; private set; }
        public EventType(string name) { Name = name; }
    }
    public class Validation : EventType {
        public const int GeneralValidation = 100;
        public const int DataValidation = 101;
        public const int UserValidation = 102;
        private Validation() : base("Validation") { }
    }
    public class Application1 : EventType {
        public const int Event1 = 1000;
        public const int Event2 = 1001;
        private Application1() : base("Application1") { }
    }
}

However, I'm not so sure that this is an approach that would follow best practices in an object oriented environment and feels kind of "hacky". I would also want to prevent the classes from being instantiated outside of the EventTypes container class (would abstract still allow the private constructor to run?), and EventTypes should not be inheritable, so sealed could apply there, but I'm not sure on that either.

Requirements

The solution I'm looking for will allow me to have a single method for logging events as I currently have. An example implementation when calling the LogEvent method would look like this:

LogEvent(EventTypes.ApplicationName.EventTypeName);
LogEvent(EventTypes.Validation.DataValidation);

The main goal is to enforce consistency in our data so that we ensure no records are lost in queries and views. The primary thing I want to avoid is having developers enter an application name manually because this can lead to inconsistency, for example:

LogEvent("Application Name", "Event Name");
LogEvent("App Name", "Evt Name");

Sure, I can use constants to prevent this, but that would require the developers to use them, and requires code reviews to ensure they are following the standard. The ideal solution would cause compilation failure if an invalid type was supplied.

The Question

This probably isn't the best phrasing; however, what options are available to replace a single enum with a solution that supports multiple types and follows best practices in an object oriented environment?

Was it helpful?

Solution

We have a repository for logging that has methods like below that can log any Type. It has similar methods for Search() and GetByID() for querying the log. With this method you don't have to specify anywhere what Types are supported other than they implement interface ICommand which in our case only has one property CreatedByID. The object passed in is serialized as json and then deserialized in methods like Search().

    public long Log<T>(T command) where T: ICommand {
        var type = GetTypeName<T>();
        var payload = JsonConvert.SerializeObject(command, Formatting.None);
        var entity = new Command { CreatedByID = command.CreatedByID, Payload = payload, Type = type };
        context.Commands.AddObject(entity);
        context.SaveChanges();
        return entity.ID;
    }

    private string GetTypeName<T>() {
        return typeof(T).FullName;
    }

OTHER TIPS

I think what you might be looking for is the typesafe enum pattern. This was a popular approach in Java prior to the introduction of the Java enum keyword. The java implementation of enum builds on this pattern and adds some features that can only be done at the language level. My understanding is that there is no corresponding feature in C# so the typesafe enum pattern could make sense here.

Here's an example based on your initial code:

public sealed class EventTypes {
    public class EventType {
        public readonly int Value { get; }
        protected EventType(int value) { Value = value; }
    }
    public sealed class Validation : EventType {
        public static readonly Validation GeneralValidation = new Validation(100);
        public static readonly Validation DataValidation = new Validation(101);
        public static readonly Validation UserValidation = new Validation(102);
        private Validation(int value) : base(value) { }
    }
    public sealed class Application1 : EventType {
        public static readonly Application1 Event1 = new Application1(1000);
        public static readonly Application1 Event2 = new Application1(1001);
        private Application1(int value) : base(value) { }
    }
}

If anything is off about the syntax here, let me know.

Here's an article that talks about this approach in C#: https://www.infoworld.com/article/3198453/how-to-implement-a-type-safe-enum-pattern-in-c.html

Allow any enum (using the System.Enum base type) and use the name of both the enum and the enum value.

public bool LogEvent(Enum eventType)
{
    Console.WriteLine
    (   
          "An event was logged. Application: {0}, Category: {1} EventType: {2}",
          eventType.GetType().Assembly.FullName,
          eventType.GetType().Name,
          eventType.ToString()
    );
    //etc....
}

This way, developers are free to define whatever enum they need for their events, and your LogEvent method can figure out where the enum came from and what it means.

Licensed under: CC-BY-SA with attribution
scroll top