0 2.7K ru

Паттерны Metadata Mapping и Query Object из каталога PoEAA

Metadata Mapping

metadata mapping

В большинстве случаев, код, управляющий объектно-реляционным распределением описывает, как поля в БД соотносятся с полями в объектах. Такой код получается сильно повторяющимся и скучным. Паттерн Metadata Mapping позволяет разработчикам определять правила распределения (маппинга) в простой табличной форме, которая может быть обработана общим кодом, который получает данные о правилах записи и чтения данных из БД.

Существует два основных варианта использования Metadata Mapping: генерация кода и метод отражения. При использовании метода генерации кода (code generation) разработчик пишет программу, которая принимает на вход метаданные и возвращает исходный код классов, выполняющих отображение. Эти классы ничем не отличаются от написанных вручную, однако на деле являются полностью искусственным "продуктом", автоматически сгенерированным в процессе сборки (как правило, перед самым началом компиляции). Полученные классы преобразователей развертываются серверным кодом. Если вы используете генерацию кода, убедитесь, что она полностью интегрирована в процесс сборки, какие бы сценарии в нем ни применялись. Сгенерированные классы никогда не должны дорабатываться вручную, а следовательно, не нуждаются в проверке программой контроля исходного кода.

При использовании метода отражения (reflection) рефлективная программа просматривает метаданные объекта, находит метод setName и выполняет его с соответствующим аргументом. Поскольку методы (и поля) воспринимаются программой как обычные данные, она может считывать имена полей и методов из файла с метаданными и затем использовать их для отображения. Обычно я не рекомендую использовать отражение — частично из-за его медлительности, однако в основном потому, что оно значительно затрудняет отладку кода. Несмотря на это, метод отражения прекрасно подходит для отображения на базу данных. Считывание имен полей и методов из файла позволяет в полной степени ощутить гибкость метода отражения.

Назначение

Использование Metadata Mapping позволяет значительно сократить объем работы, необходимый для реализации отображения на базу данных. Тем не менее подготовка отображения метаданных к работе требует некоторых усилий. Следует отметить и то, что, хотя это решение прекрасно подходит для большинства конкретных ситуаций, существуют исключения, которые значительно усложняют использование метаданных.

Metadata Mapping может конфликтовать с рефакторингом, особенно если последний происходит с использованием автоматизированных средств. Если вы измените имя закрытого поля, это может неожиданно привести к ошибке в приложении. Даже автоматизированные средства рефакторинга будут не в состоянии обнаружить имя поля в дебрях XML-метаданных. В этом плане использование генерации кода немного проще, поскольку механизмы поиска могут отследить использование поля. Тем не менее при повторной генерации кода все автоматизированные обновления будут утеряны. Разумеется, программа может предупредить вас о возможных проблемах, однако вносить изменения в метаданные придется самому. Кроме того, при использовании метода отражения вы вообще не получите никаких предупреждений. С другой стороны, Metadata Mapping может упростить рефакторинг базы данных, поскольку метаданные представляют собой спецификацию ее интерфейса. Таким образом, для согласования программы с изменениями базы данных достаточно изменить метаданные.

Объект запроса (Query Object)

Объект запроса (Query Object)

SQL зачастую сопряженно используемый язык и многие разработчики не совсем хорошо в нём разбираются. Более того, необходимо знать, как выглядит структура базы данных, чтобы создавать запросы. Можно избежать этого, создав специальные методы поиска, которые сожержат в себе весь SQL и управляются ограниченным набором параметров. Такой подход делает сложным создание и тюнинг запросов для конкретных ситуаций. Также это ведёт к дублированию в SQL-выражениях.

Паттерн Query Object - это структура объектов, которая может интерпретироваться в SQL-запрос. Можно создавать такой запрос ссылаясь на классы и поля так же как на таблицы и столбцы. Таким образом создаётся независимость разработчика от струткуры БД и конкретной реализации БД.

Query Object – это разновидность паттерна интерпритатор.

Назначение

Query Object довольно сложен в понимании, поэтому он не используются в приложениях, у которых есть собственный data layer слой . В действительности Query Object нужнен только тогда, когда вы используете Domain Model  и Data Mapper; при этом, чтобы извлечь из объектов запроса реальную пользу, понадобится и Metadata Mapping.

Настоящие преимущества Query Object раскрываются при наличии более сложных потребностей: инкапсуляции схем баз данных, поддержки работы с несколькими базами данных и оптимизации количества запросов. 

Пример Query Object на C#

По сути IQueryable в Entity Framework и есть реализацией Query Object паттерна. Более разширенный пример который я нашел тут.

// Expose one property for all the filters 
public class OrderSummaryQuery
{
    public IEnumerable<Expression<Func<OrderSummaryQueryResult, bool>>> AllFilters
    {
        get
        {
            yield return IsActiveOrderStatus;
            yield return BelongsToUser;
            yield return BelongsToCustomer;
            yield return FallsInDateRange;
        }
    }

    private Expression<Func<OrderSummaryQueryResult, bool>> BelongsToUser...
    private Expression<Func<OrderSummaryQueryResult, bool>> IsActiveOrder...
    private Expression<Func<OrderSummaryQueryResult, bool>> ForCustomer...
    private Expression<Func<OrderSummaryQueryResult, bool>> InDateRange...
}

... 

// Extension Method on IQueryable
{
    public static IQueryable<T> ApplyAllFilters<T>(
        this IQueryable<T> queryable,
        IEnumerable<Expression<Func<T, bool>>> filters)
    {
        foreach (var filter in filters)
            queryable = queryable.Where(filter);

        return queryable;
    }
}
{
    (from order in _context.Order
    join od in orderDeliveries on order.Id equals od.OrderId
    join customer in _context.Customer on order.CustomerId equals customer.Id
    select new OrderSummaryQueryResult() { Customer = customer, Order = order, OrderDelivery = od })
    .ApplyAllFilters(query.AllFilters)
    
    ...
}

 

Источник: Martin Fowler

Comments:

Please log in to be able add comments.