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