Чи варто використовувати сховище в об’єкті домену або відсунути об’єкт домену назад до сервісного шару?


10

Я прийшов із світу сценаріїв транзакцій, і я тільки починаю дивитися на DDD. Я не впевнений у правильному способі інтеграції дизайну DDD із збереженням бази даних. Ось що я маю:

Клас обслуговування під назвою OrganisationService, інтерфейс якого містить методи для отримання та збереження примірників об’єктів домену організації. Організація є сукупним коренем і має інші пов'язані з нею дані: Члени та Ліцензії. Перша база даних DBContext бази даних EF6 використовується в OrganisationService для отримання об'єктів DBB Організації та пов'язаних з ними організацій MemberDB та LicenseDB. Усі вони перетворюються в еквіваленти класу об'єктного домену, коли їх отримує OrganisationService та завантажується в об'єкт домену Організація. Цей об’єкт виглядає так:

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }
}

Я не використовую шаблон репозиторію в OrganisationService ... Я використовую сам EF як сховище, оскільки, здається, EF6 значною мірою зробив репозиторій зайвим.

На даний момент в дизайні об’єкт домену Організація є анемічним: він схожий на клас організації P POCO. Клас OrganisationService дуже схожий на сховище!

Тепер мені потрібно почати додавати логіку. Ця логіка включає управління ліцензіями та членами організацій. Тепер у дні сценарію транзакцій я б додав методи в OrganisationService для обробки цих операцій і закликав у сховище взаємодіяти з БД, але з DDD я вважаю, що ця логіка повинна бути інкапсульована в самому об'єкті домену Організації ...

Тут я не впевнений, що мені робити: мені потрібно буде зберігати ці дані назад до бази даних як частина логіки. Чи означає це, що я повинен використовувати DbContext в об'єкті домену Організація для цього? Чи погана практика використання сховища / EF у об’єкті домену? Якщо так, то де ця наполегливість належить?

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }

    public void AddLicensesToOrganisation(IList<License> licensesToAdd)
    {
        // Do validation/other stuff/

        // And now Save to the DB???
        using(var context = new EFContext())
        {
            context.Licenses.Add(...
        }

        // Add to the Licenses collection in Memory
        Licenses.AddRange(licensesToAdd);
    }
}

Чи повинен я замість цього просто мутувати об'єкт домену Організації в пам'яті, а потім відштовхувати його назад до OrganisationService? Тоді я повинен відстежувати, що насправді змінилося на об’єкті (а це те, що EF робить для власних POCO! Я начебто відчуваю, що EF - це не просто заміна сховища, але також може бути доменним шаром!)

Будь-які вказівки тут високо оцінені.

Відповіді:


2

Ви можете скористатися будь-яким підходом і змусити його добре працювати - є, звичайно, плюси і мінуси.

Entity Framework, безумовно, призначений для заповнення ваших об'єктів домену. Це добре працює, коли ваші доменні об'єкти та особи даних є однаковими класами. Набагато приємніше, якщо ви можете розраховувати на EF, щоб слідкувати за змінами, і просто телефонувати, context.SaveChanges()коли ви закінчите свою трансакційну роботу. Це також означає , що ваші атрибути перевірки не повинні бути встановлені в два рази, один раз на вашій моделі домену і один раз на ваших зберігалися особи - такі речі , як [Required]і [StringLength(x)]можуть бути перевірені в вашої бізнес - логіки , що дозволяє зловити неприпустимі стану даних , перш ніж намагатися зробити транзакцію з БД і отриматиEntityValidationException. Нарешті, це швидко кодувати - вам не потрібно писати шар відображення чи сховище, але натомість можна працювати безпосередньо з контекстом EF. Це вже сховище та одиниця роботи, тому зайві шари абстракції нічого не досягають.

Недоліком поєднання вашого домену та [NotMapped]ресурсів, що зберігаються, є те, що ви маєте купу атрибутів, розкиданих по ваших ресурсах. Часто вам потрібні властивості, що стосуються домену, які є фільтрами лише для отримання даних щодо збережених даних або встановлені пізніше у вашій бізнес-логіці та не зберігаються назад у вашій базі даних. Інколи вам потрібно буде висловити свої дані у моделях домену таким чином, що він не дуже добре працює з базою даних - наприклад, при використанні переписок Entity буде відображати їх у intстовпчик - але, можливо, ви хочете зробити карту їх легко читати string, тому вам не потрібно консультуватися з пошуком під час вивчення бази даних. Потім у вас з’являється stringвластивість, яке відображається, anenumвластивість, яка не є (але отримує рядок і відображається до enum), та API, який відкриває обидва! Точно так само, якщо ви хочете поєднати складні типи (таблиці) у різних контекстах, ви можете перетворити з mappedOtherTableId і ComplexTypeвластивістю без мапу , обидва вони піддаються публікації у вашому класі. Це може бути заплутаним для того, хто не знайомий з проектом і зайво роздуває ваші доменні моделі.

Чим складніша моя бізнес-логіка / домен, тим більш обмежуючим або громіздким я вважаю поєднання мого домену та наполегливих організацій. Для проектів із короткими термінами або, які не виражають складний бізнес-рівень, я вважаю, що використання об'єктів EF для обох цілей є доцільним, і немає необхідності абстрагувати ваш домен далеко від вашої наполегливості. Що стосується проектів, які потребують максимальної простоти розширення, або для яких потрібно виражати дуже складну логіку, я думаю, вам краще розділити їх і вирішити зайву складність.

Одним із прийомів уникнення проблеми вручну відстежувати зміни вашої організації - зберігання відповідного збереженого ідентифікатора сутності у вашій моделі домену. Це може автоматично заповнюватися вашим шаром відображення. Потім, коли вам потрібно зберегти зміну назад до EF, виберіть відповідну стійку сутність, перш ніж робити будь-яке зіставлення. Тоді, коли відображати зміни, EF виявить їх автоматично, і ви можете зателефонувати, context.SaveChanges()не відстежуючи їх вручну.

public class OrganisationService
{
    public void PersistLicenses(IList<DomainLicenses> licenses) 
    {
        using (var context = new EFContext()) 
        {
            foreach (DomainLicense license in licenses) 
            {
                var persistedLicense = context.Licenses.Find(pl => pl.Id == license.PersistedId);
                MappingService.Map(persistedLicense, license); //Right-left mapping
                context.Update(persistedLicense);
            }
            context.SaveChanges();
        }
    }
}

Чи є якась проблема при поєднанні різних підходів з різною складністю даних? якщо у вас є щось, що красиво відображає DB, просто використовуйте EF безпосередньо. Якщо у вас щось не відбувається, то введіть відображення / абстракцію перед використанням EF.
Ейфорія

@Euphoric відмінності домену та db можна обробляти під час відображення. Я не можу придумати випадок використання, коли додавання домену двічі (непостійне та стійке) та керування відстеженням змін вручну - це менше роботи, ніж додавання відображень для особливих випадків. Чи можете ви вказати мене на одного? (Я припускаю, що використання NHibernate, можливо, EF є більш обмеженим?)
Фіро

Дуже корисна, практична та інформативна відповідь. Позначення як відповідь. Дякую!
MrLane

3

Я не знаю EF6, але інші ORM поводяться з цим прозоро, тому вам не доведеться явно додавати ліцензії в контекст, він виявить їх при збереженні змін. Тож метод буде зведений до

public void AddLicenses(IEnumerable<License> licensesToAdd)
{
    // Do validation/other stuff/
    // Add to the Licenses collection in Memory
    Licenses.AddRange(licensesToAdd);
}

і код навколо нього

var someOrg = Load(orgId);

someOrg.AddLicenses(someLicences);

SaveChanges();

Об'єкти мого домену не є Суб'єктами, тому додавання їх до колекції Ліцензій, що є лише Списком, не скоротить його. Питання не стільки в EF, скільки в тому, де відбувається фактична наполегливість. Вся наполегливість в EF повинна відбуватися в контексті. Питання - куди це належить?
MrLane

2
Суб'єкти домену та об'єкти, що зберігаються, можуть / повинні бути однаковими, інакше ви вводите інший шар абстракції, який 99% часу не додає значення, а відстеження змін втрачається.
Фіро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.