Поставщики типа F# против интерфейсов C# + Entity Framework
Вопрос очень технический, и он тесно связан с различиями в 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#. Здесь у нас есть поставщики типов, которые полностью исключают необходимость создания объектов базы данных в коде: они создаются автоматически поставщиками типов!
Здорово! Но так ли это?
Во-первых, кто-то должен создать / обновить объекты базы данных, и если в проекте участвует более одного разработчика, то вполне естественно, что база данных может и будет обновляться / понижаться в разных ветвях. Насколько мне известно, это очень болезненно, когда в дело вовлечены провайдеры типа 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# для "сбоя" либо во время компиляции, либо, по крайней мере, во время тестирования, когда я не обновил базу данных, чтобы соответствовать бизнесу объекты)?
0 ответов
Как я могу легко управлять миграцией вверх / вниз базы данных в мире F#? И, для начала, как правильно осуществить миграцию базы данных в мире F#, когда в нее вовлечено много разработчиков?
Наиболее естественный способ управления миграцией базы данных - это использование инструментов, родных для базы данных, то есть простого SQL. В нашей команде мы используем пакет dbup, для каждого решения мы создаем небольшой консольный проект, чтобы свернуть миграции db в dev и во время развертывания. Потребительские приложения представлены как на F# (поставщики типов), так и на C# (EF), иногда с одной и той же базой данных. Работает как шарм.
Вы упомянули EF Code First. Все поставщики F# SQL по своей природе являются "Db First", потому что они генерируют типы на основе внешнего источника данных (базы данных), а не наоборот. Я не думаю, что смешивание двух подходов - хорошая идея. Фактически, я бы никому не рекомендовал EF Code First для управления миграциями: простой SQL проще, не требует "обширных танцев шаманов", бесконечно более гибок и понятен гораздо большему количеству людей. Если вас не устраивает создание сценариев SQL вручную и вы решили использовать EF Code First только для автоматической генерации сценария миграции, тогда даже дизайнер MS SQL Server Management Studio может сгенерировать сценарии миграции для вас.
Как F# способ достичь "лучшего из мира C#", как описано выше: когда я обновляю Person типа F#, а затем исправляю все места, где мне нужно добавлять / удалять свойства записи, какой будет наиболее подходящий способ F# для "Сбой" во время компиляции или, по крайней мере, во время тестирования, когда я не обновил базу данных, чтобы она соответствовала бизнес-объектам?
Мой рецепт выглядит следующим образом:
- Не используйте интерфейсы. как ты сказал:)
интерфейсы, он просто не чувствует путь F#
- Не позволяйте автоматически сгенерированным типам из провайдера типов просачиваться за пределы тонкого уровня доступа к БД. Они не являются бизнес-объектами, и ни одна из сущностей EF не является по сути.
- Вместо этого объявите записи F# и / или дискриминируемые объединения как объекты вашего домена. Смоделируйте их, как вам угодно, и не чувствуйте себя стесненными схемой БД.
- В слое доступа к БД сопоставьте автоматически сгенерированные типы БД с типами F# вашего домена. Каждое использование типов, автоматически сгенерированных провайдером типов, начинается и заканчивается здесь. Да, это означает, что вы должны написать сопоставления вручную и ввести здесь человеческий фактор, например, вы можете случайно сопоставить FirstName и LastName. На практике это крошечные накладные расходы, и выгоды от развязки перевешивают их на величину.
- Как убедиться, что вы не забыли нанести на карту какую-либо недвижимость? Это невозможно, F# компилятор выдаст ошибку, если запись не полностью инициализирована.
- Как добавить новое свойство и не забыть его инициализировать? Начните с кода F#: добавьте новое свойство в запись / записи домена, компилятор F# проведет вас по всем экземплярам записей (обычно только один) и заставит инициализировать его чем-то (вам придется соответствующим образом добавить сценарий миграции / обновить схему базы данных).
- Как удалить свойство и не забудьте очистить все до схемы БД. Начните с другого конца: удалите столбец из базы данных. Все сопоставления между типами поставщиков типов и записями домена F# будут нарушены и выделены свойства, которые стали избыточными (что еще более важно, это заставит вас дважды проверить, действительно ли они избыточны, и пересмотреть ваше решение).
- Фактически в некоторых сценариях вы можете захотеть сохранить столбец базы данных (например, для исторических целей или в целях аудита) и удалить только свойство из кода F#. Это всего лишь один (и довольно редкий) из множества сценариев, когда удобно отделить модель предметной области от схемы БД.
Короче говоря
- миграции через простой SQL
- типы доменов объявляются вручную записями F#
- ручное сопоставление провайдеров типов с типами доменов F#
Даже короче
Придерживайтесь принципа единой ответственности и наслаждайтесь преимуществами.