Вопрос очень технический, и он тесно связан с различиями в F #/С#. Вполне вероятно, что я мог что-то пропустить. Если вы обнаружите концептуальную ошибку, пожалуйста, прокомментируйте, и я обновлю вопрос.
Давайте начнем с мира С#. Предположим, что у меня есть простой бизнес-объект, назовите его 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; }
}
вместе с соответствующими атрибутами и кодом, связанными с базой данных, которые я для краткости IPerson
, а затем использую Reflection для копирования свойств интерфейса IPerson
между Person
и EFPerson
ИЛИ просто используйте EFPerson
(переданный как IPerson
) напрямую ИЛИ делайте что-то еще. Это довольно неактуально, поскольку потребители всегда видят IPerson
и поэтому реализация может быть изменена в любое время, когда потребители ничего не знают об этом.
Если мне нужно добавить свойство, то я сначала IPerson
интерфейс IPerson
(скажем, я добавляю свойство DateTime DateOfBirth { get; set; }
), а затем компилятор скажет мне, что исправить. Однако, если я удалю свойство из интерфейса (допустим, мне больше не нужно LastName
), компилятор мне не поможет. Однако я могу написать тест на основе отражений, который обеспечит идентичность свойств IPerson
, Person
, EFPerson
и т.д. Это на самом деле не нужно, но это можно сделать, и тогда это будет работать как по волшебству (и да, у нас есть такие тесты, и они работают как по волшебству).
Теперь давайте перейдем к миру F #. Здесь у нас есть поставщики типов, которые полностью исключают необходимость создания объектов базы данных в коде: они создаются автоматически поставщиками типов!
Здорово! Но так ли это?
Во-первых, кто-то должен создать/обновить объекты базы данных, и если в проекте участвует более одного разработчика, то вполне естественно, что база данных может и будет обновляться/понижаться в разных ветвях. Насколько мне известно, это очень болезненно, когда в дело вовлечены провайдеры типа F #. Даже если для обработки миграций используется С# EF Code First, для "счастья" провайдеров F #-типа требуются "обширные танцы шаманов".
Во-вторых, в мире F # все является неизменным по умолчанию (если только мы не делаем его изменяемым), поэтому мы явно не хотим передавать изменяемые объекты базы данных вверх по течению. Это означает, что, как только мы загружаем изменяемую строку из базы данных, мы хотим как можно скорее преобразовать ее в "нативную" неизменяемую структуру F #, чтобы она работала только с чистыми функциями в восходящем направлении. В конце концов, использование чистых функций уменьшает количество необходимых тестов, я думаю, в 5 - 50 раз, в зависимости от домена.
Давайте вернемся к нашей Person
. Я пока пропущу все возможные переопределения (например, целое число базы данных в случае F # DU и тому подобное). Итак, наш F # Person
будет выглядеть так:
type Person =
{
personId : int
name : string
lastName : string
}
Итак, если "завтра" мне нужно добавить dateOfBirth: DateTime
к этому типу, то компилятор скажет мне обо всех местах, где это нужно исправить. Это здорово, потому что компилятор С# не скажет мне, где мне нужно добавить эту дату рождения, кроме базы данных. Компилятор F # не скажет мне, что мне нужно пойти и добавить столбец базы данных в таблицу Person
. Однако в С#, поскольку мне сначала нужно обновить интерфейс, компилятор скажет мне, какие объекты должны быть исправлены, включая один или несколько объектов базы данных.
Судя по всему, я хочу лучшего из обоих миров в F #. И хотя этого можно добиться с помощью интерфейсов, он просто не чувствует F #. В конце концов, аналог DI/IOC выполняется совсем по-другому в F # и обычно достигается передачей функций, а не интерфейсов.
Итак, вот два вопроса.
-
Как я могу легко управлять миграцией вверх/вниз базы данных в мире F #? И, для начала, как правильно осуществить миграцию базы данных в мире F #, когда в нее вовлечено много разработчиков?
-
Как F # способ достичь "лучшего из мира С#", как описано выше: когда я обновляю
Person
типа F #, а затем исправляю все места, где мне нужно добавлять/удалять свойства записи, какой будет наиболее подходящий способ F # для "сбой" или во время компиляции, или, по крайней мере, во время тестирования, когда я не обновил базу данных, чтобы она соответствовала бизнес-объектам?