Поставщики типа F # против интерфейсов C # + Entity Framework - PullRequest
0 голосов
/ 21 января 2019

Вопрос очень технический, и он тесно связан с различиями в F # / C #. Вполне вероятно, что я что-то пропустил. Если вы обнаружите концептуальную ошибку, пожалуйста, прокомментируйте, и я обновлю вопрос.

Давайте начнем с мира C #. Предположим, что у меня есть простой бизнес-объект, назовите его Person (но, пожалуйста, имейте в виду, что есть 100+ объектов, намного более сложных, чем в бизнес-сфере, с которой мы работаем):

public class Person : IPerson
{
    public int PersonId { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
}

и я использую DI / IOC, поэтому я никогда не передаю Person. Скорее, я бы всегда использовал интерфейс (упомянутый выше), называя его IPerson:

public interface IPerson
{
    int PersonId { get; set; }
    string Name { get; set; }
    string LastName { get; set; }
}

Бизнес-требование заключается в том, чтобы лицо могло быть сериализовано / десериализовано из базы данных. Допустим, я решил использовать Entity Framework для этого, но фактическая реализация кажется неуместной для вопроса. На этом этапе у меня есть возможность ввести класс (ы), связанные с базой данных, например, EFPerson

public class EFPerson : IPerson
{
    public int PersonId { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
}

вместе с соответствующими атрибутами базы данных и кодом, который я пропущу для краткости, а затем использую Reflection для копирования свойств интерфейса IPerson между Person и EFPerson ИЛИ просто используйте EFPerson (передается как IPerson) напрямую ИЛИ делать что-то еще. Это не имеет значения, так как потребители всегда будут видеть IPerson, и поэтому реализация может быть изменена в любое время, когда потребители ничего не знают об этом.

Если мне нужно добавить свойство, то я сначала обновлю интерфейс IPerson (скажем, я добавляю свойство DateTime DateOfBirth { get; set; }), а затем компилятор скажет мне, что исправить. Однако, если я удалю свойство из интерфейса (скажем, мне больше не нужен LastName), компилятор мне не поможет. Однако я могу написать тест на основе отражений, который обеспечит идентичность свойств IPerson, Person, EFPerson и т. Д. Это на самом деле не нужно, но это можно сделать, и тогда оно будет работать как по волшебству (и да, у нас есть такие тесты, и они работают как по волшебству).

Теперь давайте перейдем к миру F #. Здесь у нас есть поставщики типов, которые полностью исключают необходимость создания объектов базы данных в коде: они создаются автоматически поставщиками типов!

Cool! Но так ли это?

Во-первых, кто-то должен создать / обновить объекты базы данных, и если в нем участвует более одного разработчика, то вполне естественно, что база данных может и будет обновляться / понижаться в разных ветвях. Насколько мне известно, это очень болезненно, когда в дело вовлечены провайдеры типа F #. Даже если для обработки миграций используется C # EF Code First, для «счастья» провайдеров F #-типа требуются «обширные танцы шаманов».

Во-вторых, в мире F # все является неизменным по умолчанию (если мы не делаем его изменяемым), поэтому мы явно не хотим передавать изменяемые объекты базы данных в обратном направлении. Это означает, что, как только мы загружаем изменяемую строку из базы данных, мы хотим как можно скорее преобразовать ее в «родную» неизменяемую структуру F #, чтобы она работала только с чистыми функциями в восходящем направлении. В конце концов, использование чистых функций уменьшает количество необходимых тестов, я думаю, в 5 - 50 раз, в зависимости от домена.

Давайте вернемся к нашему Person. Я пока пропущу все возможные переопределения (например, целое число базы данных в случае F # DU и тому подобное). Итак, наш F # Person будет выглядеть так:

type Person =
    {
        personId : int
        name : string
        lastName : string
    }

Итак, если «завтра» мне нужно добавить dateOfBirth : DateTime к этому типу, то компилятор сообщит мне обо всех местах, где это нужно исправить. Это здорово, потому что компилятор C # не скажет мне, где мне нужно добавить эту дату рождения, кроме базы данных. Компилятор F # не скажет мне, что мне нужно пойти и добавить столбец базы данных в таблицу Person. Однако в C #, так как мне сначала нужно обновить интерфейс, компилятор скажет мне, какие объекты должны быть исправлены, включая один или несколько объектов базы данных.

Видимо, я хочу лучшего от ботач миры в F #. И хотя этого можно добиться с помощью интерфейсов, он просто не чувствует F #. В конце концов, аналог DI / IOC выполняется совсем по-другому в F # и обычно достигается передачей функций, а не интерфейсов.

Итак, вот два вопроса.

  1. Как я могу легко управлять миграциями вверх / вниз базы данных в мире F #? И, для начала, как правильно осуществить миграцию базы данных в мире F #, когда в нее вовлечено много разработчиков?

  2. Как F # способ добиться «лучшего из мира C #», как описано выше: когда я обновляю тип F # Person, а затем исправляю все места, где мне нужно добавлять / удалять свойства записи, что было бы наиболее подходящим способом F # «провалиться» либо во время компиляции, либо, по крайней мере, во время тестирования, когда я не обновил базу данных, чтобы она соответствовала бизнес-объекту (ам)?

...