4
\$\begingroup\$

The core of this question is removing a Static Class smell from my Web-Api/MVC solution. It presently works but feels kind of dirty.

Present Solution

Standard solution with 6 projects:

  • MVC
  • Api
  • Api.Client - wrapped client to make in-code calls
  • Api.Model - Dto's, Request models, etc.
  • Domain - Entity layer, services, client mocks
  • Logging

In my domain layer I have a simple Asset.

using Constants.Taxonomy1;

public class Asset : TrackedEntity, IEntity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }

    [StringLength(128, MinimumLength = 1)]
    [Required]
    [Index("IX_NameTypeAndParent", 2, IsUnique = true)]
    public string Name { get; set; }

    [Required]
    [Index("IX_NameTypeAndParent", 3, IsUnique = true)]
    public AssetType Type { get; set; }

    [Index("IX_NameTypeAndParent", 1, IsUnique = true)]
    public Guid? AssetId { get; set; }

    public virtual Asset Parent { get; set; }

    public virtual ICollection<Asset> Children { get; set; }
}

A Guid, a name and an enum AssetType. The AssetType defines what other types of Asset can be its parent or children. This behaviour is currently defined in an external library; the Constants.Taxonomy1 import. This is separate from the solution so that it can be reused elsewhere and contains mappings between the various Taxonomies that we used.

The Taxonomy, in its cut down version, looks like this:

namespace Constants.Taxonomy1
{
    using System.Collections.Generic;

    public enum AssetType : int
    {
        Undefined,
        WTG,
        WindFarm,
        MeteorologicalStation
    }

    public static class Taxonomy
    {
        private static Dictionary<AssetType, List<AssetType>> childRelationships = new Dictionary<AssetType, List<AssetType>>()
        { 
            { AssetType.Undefined, new List<AssetType> { } },
            { AssetType.WTG, new List<AssetType> { } },
            { AssetType.WindFarm, new List<AssetType> { AssetType.WTG, AssetType.MeteorologicalStation } },
            { AssetType.MeteorologicalStation, new List<AssetType> { } }
        };

        private static Dictionary<AssetType, AssetType> parentRelationships = new Dictionary<AssetType, AssetType>()
        {
            { AssetType.Undefined, AssetType.Undefined },
            { AssetType.WTG, AssetType.WindFarm },
            { AssetType.WindFarm, AssetType.Undefined },
            { AssetType.MeteorologicalStation, AssetType.WindFarm }
        };

        public static AssetType Parent(AssetType assetType)
        {
            return Taxonomy.parentRelationships[assetType];
        }

        public static List<AssetType> Children(AssetType assetType)
        {
            return Taxonomy.childRelationships[assetType];
        }
    }
}

Pretty simple, an enum of the asset type, a dictionary of what its parent is allowed to be and one for allowable children types. Retrieving the information is through a call to Taxonomy.Parent(AssetType type) or Taxonomy.Children(AssetType type).

Different Taxonomy rules are placed in their own namespaces in this library.

  • Constants.Basic
  • Constants.Taxonomy1
  • Constants.Taxonomy2
  • Constants.FarTooMany

Each of which have an enum AssetType, Taxonomy.Parent(...) and Taxonomy.Children(...), so the Taxonomy rules can be switched by changing which namespace is loaded.

The Smell

OK, so this works but... I end up having to import Constants.xxxx whenever I need the Enumeration type or want to check the heritage of an Asset. This leads to the library being used liberally through the solution, MVC, Api, Domain, Client and Model layers. It feels wrong.

Ideally the enumeration type and taxonomy behaviour should be defined down in the Domain, eliminating the need to call static methods and the shotgun spread of using Constants.xxxx in the other projects.

Options?

I've tried out several options yesterday including:

  • Creating my own Generic type which wraps the enum and Taxonomy behaviour; but this can't be stored within the Asset entity as it's a complex type.

  • Creating an Interface that the Entity should implement, this led to a lot of dead-end solutions, including injecting an implementation of the interface into the Entity constructor through Autofac, which knackers up the repository pattern being used to manage the entities. This falls foul of the "defining behaviours on Entities" Holy War which made for some "Duck and Cover" reading.

  • Considering a Singleton to hold the Taxonomy rules, which allows it be defined in the Domain layer, but retains the drawbacks of being static.

Potential Solution

I'm currently thinking that the Asset Type needs to be resolved in the services layer through which the entities are manipulated. The Asset would do away with the enumeration field and hold just a plain Integer.

The service layer on picking up the Asset will take the integer value and use this with a Taxonomy service to determine the Asset type and Parent/Child restrictions.

The Taxonomy service could then take the IAssetType interface (implementation defined in Autofac) in order to apply the desired Taxonomy behaviour to the Asset?

But, then again, I lose the convenience of being able to use an Enum when writing the MVC and Web-Api sections of the Solution.

Final Plea

As in the heading above, any help much appreciated.

\$\endgroup\$
3
\$\begingroup\$

This feels like you want more than an enum. You can create an abstract class with a protected constructor. A few private sub classes later you have both an enum and objects with behavior directly modeling the parent-child relationships you are looking for:

public abstract class AssetType
{
    public static readonly AssetType Undefined = new GenericAssetType(0, "Undefined");
    public static readonly AssetType WTG = new GenericAssetType(1, "WTG", AssetType.WindFarm);
    public static readonly AssetType WindFarm = new WindFarmAssetType(2, "WindFarm");
    public static readonly AssetType MeteorlogicalStation = new GenericAssetType(3, "MeteorlogicalStation", AssetType.WindFarm);

    public int Value { get; private set; }
    public string Text { get; private set; }
    public IEnumerable<AssetType> ChildRelationships { get; protected set; }
    public AssetType ParentRelationship { get; protected set; }

    protected AssetType(int value, string text) : this(value, text, AssetType.Undefined)
    {
    }

    protected AssetType(int value, string text, AssetType parentRelationship)
    {
        Value = value;
        Text = text;
        ParentRelationship = parentRelationship;
    }

    public override string ToString()
    {
        return Text;
    }

    static public explicit operator int(AssetType assetType)
    {
       return assetType.Value;
    }

    static public implicit operator string(AssetType assetType)
    {
       return assetType.Text;
    }

    private class GenericAssetType : AssetType
    {
        protected GenericAssetType(int value, string text) : base(value, text, AssetType.Undefined)
        {
            ChildRelationships = new AssetType[0];
        }
    }

    private class WindFarmAssetType : AssetType
    {
        public override List<AssetType> ChildRelationships { get; private set; }

        protected WindFarmAssetType(int value, string text) : base(value, text, AssetType.Undefined)
        {
            ChildRelationships = new AssetType[2]
            {
                AssetType.WTG,
                AssetType.MeteorlogicalStation
            };
       }
    }
}

Now you can refer to each AssetType as if it were an enum:

AssetType.Undefined
AssetType.WindFarm
AssetType.WindFarm.ChildRelationships.ElementAt(0) // = AssetType.WTG
AssetType.WTG.ParentRelationship                   // = AssetType.WindFarm

By implementing custom explicit casts to int and string, you can even cast them:

int value = (int)AssetType.WindFarm;          // = 2
string text = (string)AssetType.WindFarm;     // = "WindFarm"
string text2 = AssetType.WindFarm.ToString(); // = "WindFarm"

By making the ChildRelationships an IEnumerable<AssetType> with a protected setter, you ensure outside code is unable to add or remove child relationships.

Because the AssetType class has a protected constructor, you also limit the instances that are available to the system to just those you want.

\$\endgroup\$
  • \$\begingroup\$ Thanks @greg, that certainly resolves the static class issue, though I'm bit worried about setting up the full Taxonomy which was ~360 AssetTypes :-). What I need to think about now is how you keep the state of the AssetType in an Entity model. I imagine the int will be stored, as with normal enums, as a plain int value. I think I saw some articles on using Generic or Abstract types that could be a starting point. It's a long way from storing basic strings and doubles! \$\endgroup\$ – Ross Halliday Oct 15 '15 at 9:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.