3

Все объекты в домене должны иметь личность. Наследуя от DomainEntity, я могу предоставить идентичность классам.Установка личности субъекта домена

Город домен объект (урезанная для легкого чтения):

public class City : DomainEntity, IAggregateRoot 
{ 
    public string Name { get; private set; } 

    public Coordinate Coordinate { get; private set; } 

    public City(string name, decimal latitude, decimal longitude) 
    { 
     Name = name; 
     SetLocation(latitude, longitude); 
    } 

    public City(string name, decimal latitude, decimal longitude, int id) 
     : base(id) 
    { 
     Name = name; 
     Coordinate = coordinate; 
     SetLocation(latitude, longitude); 
    } 

    public void SetLocation(decimal latitude, decimal longitude) 
    { 
     Coordinate = new Coordinate(latitude, longitude); 
    } 
} 

DomainEntity абстрактный класс:

public abstract class DomainEntity 
{ 
    private int? uniqueId; 

    public int Id 
    { 
     get 
     { 
      return uniqueId.Value; 
     } 
    } 

    public DomainEntity() 
    { } 

    public DomainEntity(int id) 
    { 
     uniqueId = id; 
    } 
} 

Когда новый объект сначала создается, идентичность не существует , Идентификация будет существовать только после сохранения объекта. Из-за этого при создании нового экземпляра объекта, Id не нужно подавать:

var city = new City("Cape Town", 18.42, -33.92); 

Когда города считываются с сохранением с помощью CityRepository, то второй конструктор будет использоваться так, чтобы заполнить личность а также:

public class CityRepository : ICityRepository 
{ 
    public City Find(int id) 
    { 
     var cityTblEntity = context.Set<CityTbl>().Find(id); 

     return new City(cityTblEntity.Name, cityTblEntity.Lat, cityTblEntity.Long, cityTblEntity.Id); 
    } 
} 

Проблема, с которой я столкнулась, заключается в том, что я предоставляю конструктор, который может принимать идентичность. Это открывает отверстие. Я хочу, чтобы идентификатор был установлен на уровне репозитория, но клиентский код теперь также может начать устанавливать значения Id. Что мешает кому-то делать это:

var city = new City("Cape Town", 18.42, -33.92, 99999); // What is 99999? It could even be an existing entity! 

Как я могу обеспечить способы установки идентичности лица в моем хранилище, но скрыть это от клиентского кода? Возможно, мой дизайн испорчен. Могу ли я использовать заводы для решения этой проблемы?

Примечание: Я понимаю, что это не идеальная реализация DDD, поскольку сущности должны иметь идентификацию с самого начала. Тип Guid поможет мне решить эту проблему, но к сожалению, у меня нет этой роскоши.

+0

Если это приемлемо, вы можете оставить это до ORM? Я сам не являюсь участником Framework Framework, но с NHibernate вы можете выбрать сопоставление столбца (идентификатор в вашем случае) с частным полем, а затем предоставить свойство для чтения только для чтения. Кажется, есть аналогичный вариант в Entity Framework: http://blog.oneunicorn.com/2012/03/26/code-first-data-annotations-on-non-public-properties/ – lasseeskildsen

ответ

4

В дополнение к ответу Ильи Палкина я хочу опубликовать еще одно решение, которое проще, но немного сложнее:

  1. сделать DomainEntity.UniqueId защищены, так что он может быть доступ к его детям
  2. Внесите заводские (или статические заводские методы) и определите его внутри класса City, чтобы он мог получить доступ к защищенному полю DomainEntity.UniqueId.

Плюсы: Без отражения, код можно проверить.
Минусы: Уровень домена знает о слое DAL. Немного сложное определение фабрики.

Код:

public abstract class DomainEntity 
{ 
    // Set UniqueId to protected, so you can access it from childs 
    protected int? UniqueId; 
} 

public class City : DomainEntity 
{ 
    public string Name { get; private set; } 

    public City(string name) 
    { 
     Name = name; 
    } 

    // Introduce a factory that creates a domain entity from a table entity 
    // make it internal, so you can access only from defined assemblies 
    // also if you don't like static you can introduce a factory class here 
    // just put it inside City class definition 
    internal static City CreateFrom(CityTbl cityTbl) 
    { 
     var city = new City(cityTbl.Name); // or use auto mapping 
     // set the id field here 
     city.UniqueId = cityTbl.Id; 
     return city; 
    } 
} 

public class CityTbl 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

static void Main() 
{ 
    var city = new City("Minsk"); 

    // can't access UniqueId and factory from a different assembly 
    // city.UniqueId = 1; 
    // City.CreateFrom(new CityTbl()); 
} 

// Your repository will look like 
// and it won't know about how to create a domain entity which is good in terms of SRP 
// You can inject the factory through constructor if you don't like statics 
// just put it inside City class 
public class CityRepository : ICityRepository 
{ 
    public City Find(int id) 
    { 
     var cityTblEntity = context.Set<CityTbl>().Find(id); 

     return City.CreateFrom(cityTblEntity); 
    } 
} 
+0

Это хорошее решение, если ваша модель домена знает о модели данных Entity Framework, несмотря на то, что многие люди, как правило, не знают о стойкости к доменной модели. –

+0

@ Илья Палкин. Вы правы, добавлены к минусам. –

0

Я вижу следующие варианты:

  1. статический метод на Entity типа, который имеет доступ к закрытым полям.

    public class Entity 
    { 
        private int? id;    
        /* ... */   
        public static void SetId(Entity entity, int id) 
        { 
         entity.id = id; 
        } 
    } 
    

    Использование:

    var target = new Entity(); 
        Entity.SetId(target, 100500); 
    
  2. Отражение можно использовать, чтобы получить доступ к частной области

    public static class EntityHelper 
    { 
        public static TEntity WithId<TEntity>(this TEntity entity, int id) 
         where TEntity : Entity 
        { 
         SetId(entity, id); 
         return entity; 
        } 
    
        private static void SetId<TEntity>(TEntity entity, int id) 
         where TEntity : Entity 
        { 
         var idProperty = GetField(entity.GetType(), "id", BindingFlags.NonPublic | BindingFlags.Instance); 
         /* ... */ 
         idProperty.SetValue(entity, id); 
        } 
    
        public static FieldInfo GetField(Type type, string fieldName, BindingFlags bindibgAttr) 
        { 
         return type != null 
          ? (type.GetField(fieldName, bindibgAttr) ?? GetField(type.BaseType, fieldName, bindibgAttr)) 
          : null; 
        } 
    } 
    

    Usege:

    var target = new Entity().WithId(100500); 
    

    Полный код доступен как gist on GitHub.

  3. Automapper может использоваться поскольку он использует отражение и может отображать частные объекты.

    Я проверил это отвечать How to retrieve Domain Object from Repositories

    [TestClass] 
    public class AutomapperTest 
    { 
        [TestMethod] 
        public void Test() 
        { 
         // arrange 
         Mapper.CreateMap<AModel, A>(); 
         var model = new AModel { Value = 100 }; 
    
         //act 
         var entity = Mapper.Map<A>(model); 
    
         // assert 
         entity.Value.Should().Be(100); 
         entity.Value.Should().Be(model.Value); 
        } 
    } 
    
    public class AModel 
    { 
        public int Value { get; set; } 
    } 
    
    public class A 
    { 
        public int Value { get; private set; } 
    } 
    

PS: Реализация DomainEntity.Id может привести к InvalidOperationException когда uniqueId не установлен.

EDIT:

Но, в данном случае, не будет фабричные методы только обеспечивают тонкую фанеру над каждым конструктором? Я всегда знал фабрики, которые будут использоваться для создания сложных экземпляров атомарно, чтобы не нарушались «правила» домена, возможно, сущности с ассоциациями и агрегациями.

Эти заводские методы могут использоваться для создания новых экземпляров в вашей системе. Преимущество состоит в том, что можно дать им любое имя с четким описанием. К сожалению, их сложно издеваться, поскольку они статичны.

Если тестируемость является целью, то могут быть разработаны отдельные заводы.

Factories have several benefits over constructors:

  • заводы могут сказать об объектах, они создают
  • заводы полиморфные, в том, что они могут вернуть объект или любой подтип объекта, созданного
  • В случаях, когда у создаваемого объекта есть много необязательных параметров, у нас может быть объект Builder как Factory

Было бы здорово, если бы я мог использовать фабрики для создания новых экземпляров с идентичностью, но разве эти заводы еще не должны были бы называть эти публичные конструкторы с обломочным идентификатором?

Я думаю, что что-то public необходимо в любом случае, если вы не используете отражение.

Может быть another solution. Вместо публичного конструктора ваши сущности могут apply перегибом commnd или «спецификациями» с значением id.

public void Apply(AppointmentCreatedFact fact) 
    { 
     Id = fact.Id; 
     DateOfAppointment = fact.DateOfAppointment; 
    } 

Но я preffer static метода по типу «Entity», так как это не так очевидно для вызова.

Я не думаю, что общественный конструктор - это зло. Это зло, когда конструктор вызывается во многих местах, и добавление нового параметра в него приводит к бесконечной фиксации ошибок компиляции. Я предлагаю вам контролировать места, где вызываются конструкторы объектов домена.

+0

Спасибо, что ответите Илье. Почему вы используете статический метод в своем первом решении? И разве это еще не будет доступно из клиентского кода? Ваше второе решение имеет больше смысла. Я мог бы сделать «EntityHelper» «внутренним» классом, тем самым ограничив его уровнем Infrastucture. Я все еще чувствую, что размышление - это хакерский способ обойти это. Я чувствую, что мой дизайн испорчен. – davenewza

+0

1-е решение не требует отражения и статично напечатано. Да, он доступен, но это не так очевидно. Второе решение может быть сделано внутренним, но оно использует «магическую строку» (например, «id»). Когда я разработал свой собственный DAL, у меня был один конструктор для каждого объекта со всеми свойствами. Чтобы создать новые экземпляры в системе [Factory Method] (http://sourcemaking.com/refactoring/replace-constructor-with-factory-method), можно использовать. –

+0

Возможно, использование фабрик - это ответ. Но, в этом случае, не будут ли фабричные методы просто предоставлять тонкий шпон по каждому конструктору? Я всегда знал фабрики, которые будут использоваться для создания сложных экземпляров атомарно, чтобы не нарушались «правила» домена, возможно, сущности с ассоциациями и агрегациями. – davenewza

0

У меня такое ощущения, что нуль ID удовлетворяет тождество. - то есть, это нового или потенциал сущности.Я хотел бы использовать один конструктор следующим образом:

public City(string name, decimal latitude, decimal longitude, int? id = null) 
    : base(id) 
{ 
    Name = name; 
    Coordinate = coordinate; 
    SetLocation(latitude, longitude); 
} 
0

Самое простое решение является марка все конструкторы с Id, как внутренняя, которая требует минимальных изменений.

public class City : DomainEntity, IAggregateRoot 
{ 
    public City(string name, decimal latitude, decimal longitude) 
    { 
     Name = name; 
     SetLocation(latitude, longitude); 
    } 

    // Just make it internal 
    internal City(string name, decimal latitude, decimal longitude, int id) 
     : base(id) 
    { 
     Name = name; 
     Coordinate = coordinate; 
     SetLocation(latitude, longitude); 
    } 
} 
Смежные вопросы