none
Changing interface signature without editing code in multiple places RRS feed

  • Question

  • I have an interface and existing code which implements this interface:

    public interface IBusinessService<T> where T : class
    {
        Task Add(T category);
    
        Task Delete(T category);
    
        Task Update(T category);
    
        Task<IEnumerable<T>> GetAll();
    
        Task<T> GetById(int id);     
    }

    Now some id has type of Guid. So I cannot use this method to send Guid id:

    var id = Guid.NewGuid()
    var result = GetById(id);

    So we need that id parameter can be type of Guid. It would be ideal if it is possible:

    public interface IBusinessService<T> where T : class
    {
        /* ... the other code is omitted for the brevity */
        Task<T> GetById(Guid or int id );     
    }

    What I thought to implement is to create a new method with parameter type of Guid:

    public interface IBusinessService<T> where T : class
    {
        Task Add(T category);
    
        Task Delete(T category);
    
        Task Update(T category);
    
        Task<IEnumerable<T>> GetAll();
    
        Task<T> GetById(int id);     
    
        Task<T> GetById(Guid id);     
    }

    But if I will do this, then I need to edit so many code. So it looks like it is not a good solution.

    Is there a way to add another type of id to the interface method GetById without breaking changes?



    • Edited by NiceStepUp Friday, January 3, 2020 3:56 PM
    Friday, January 3, 2020 2:01 PM

All replies

  • You're going to have to do some kind of editing becuase you're using an interface that's being implemented on a class.

    Maybe optional parameter can help ease the pain where you look at one or the other, like if int id = 0 on an optional, then use Guid ID or vice versa.

    https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments

    Friday, January 3, 2020 2:25 PM
  • Why did `id` suddenly become a Guid? Is this for 1 specific implementation? If so then perhaps you need to specialize your service into a new interface that allows the key to be specified as well. Then `IBusinessService<T>` is just a derived interface that has a key of type `int`. Unfortunately this will likely make working with the interface harder if you have a lot of code already using the original interface but now needs to support this new interface instead. Alternatively have your "specific implementation" handle the Guid translation behind the scenes by accepting an int (so the interface remains the same) and then map that int to the guid using a table or something. This would be a short term solution while you deprecate the old interface.

    If you're running C# 8 (.NET Core) then you can also look into using default implementations in interfaces. This would allow you to add the method to the existing interface but you still have the challenge of not being able to map a Guid to an int so the default implementation would probably do nothing.

    Long term you probably just want to use a string for an id and then any type can be used in an implementation.


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, January 3, 2020 4:05 PM
    Moderator
  • I think this interface is design smell. At first Add method should be named as Insert. At second there is reason to not to use GetById and GetAll methods in generic interface. It is because you can have table with many records and GetAll method could take a lot of time. 

    GetById method should be called GetByPrimaryKey(PrimaryKey primaryKey). Primary key object holds columns and values. I could be easy to implement by Dictionary<string, object>.

    public class PrimaryKey { private readonly Dictionary<string, object> columnValues = new Dictionary<string, object>(); public PrimaryKey(string columnName, object value) { columnValues.Add(columnName, value); } public KeyValuePair<string, object> ColumnValues => dictionary;

    public string WhereCondition => string.Join(" and ", columnValues.Keys.Select(k=>k+" = @" + k));

    }

    In GetByPrimaryKey method you will it use like this:

    class CategoryDao<T>
    {
        public T GetByPrimaryKey(PrimaryKey primaryKey)
        {
            using(SqlCommand command = new SqlCommand(...))
            {
                command.CommandText="select * from Category where  "+primaryKey.WhereCondition;
                foreach(var columnValue in primaryKey.ColumnValues)
                {
                    command.Parameters.AddWithValue(columnValue.Key, columnValue.Value);
                }
            }
        }
    }

    In relation to change meaning and insight into your code you need to change all places where interface is. It is terrible but it is best practices described in DDD by Eric Evans.

    It is easy when you have database where all tables has primary key as one column. We have database that tables have compound keys with multiple columns. We use this model but constructor of PrimaryKey can pass tuple array as parameter. 

    • Edited by Petr B Sunday, January 5, 2020 10:49 PM
    Sunday, January 5, 2020 10:46 PM