ОРМ и слои - PullRequest
       38

ОРМ и слои

5 голосов
/ 29 мая 2009

Извините за то, что этот момент здесь повсюду ... но я чувствую себя как собака, гоняющаяся за моим хвостом, и я все в замешательстве в этот момент.

Я пытаюсь найти самый чистый способ разработки трехуровневого решения (IL, BL, DL), где DL использует ORM для абстрагирования доступа к БД.

Везде, где я видел, люди используют LinqToSQL или LLBLGen Pro для генерации объектов, которые представляют таблицы БД, и ссылаются на эти классы во всех 3 слоях. Похоже, что 40 лет шаблонов кодирования были проигнорированы - или произошел сдвиг парадигмы, и я пропустил объяснительную часть о том, почему это совершенно нормально.

Тем не менее, похоже, что есть еще основания для желания быть независимым от механизма хранения данных - посмотрите, что только что произошло с LinqToSQL: против него написано много кода - только для MS бросить его ... Так что я хотел бы выделить часть ORM как можно лучше, просто не знаю как.

Итак, возвращаясь к абсолютным основам, вот основные части, которые я хотел бы собрать очень чистым способом:

Сборки, с которых я начинаю: UL.dll BL.dll DL.dll

Основные классы:

Класс Message, имеющий свойство, представляющее коллекцию (называемую MessageAddresses) объектов MessageAddress:

class Message 
{
    public MessageAddress From {get;}
    public MessageAddresses To {get;}
}

Функции на слой:

BL предоставляет метод для пользовательского интерфейса, называемый GetMessage (идентификатор Guid), который возвращает экземпляр сообщения.

BL в свою очередь оборачивает DL.

DL имеет ProviderFactory, которая оборачивает экземпляр Provider. DL.ProviderFactory выставляет (возможно ... часть моих вопросов) два статических метода, называемых GetMessage (идентификатор Guid) и SaveMessage (Сообщение сообщения) Конечной целью было бы иметь возможность поменять поставщика, который был написан для Linq2SQL, на одного для LLBLGen Pro или другого поставщика, который не работает с ORM (например, VistaDB).

Дизайн Цели: Я хотел бы разделение слоев. Я хотел бы, чтобы каждый слой имел зависимость только от слоя ниже, а не над ним. Я хотел бы, чтобы сгенерированные ORM классы были только на уровне DL. Я хотел бы, чтобы UL поделился классом сообщений с BL.

Следовательно, означает ли это, что:

а) Сообщение определено в BL b) Представление Db / Orm / Manual таблицы БД («DbMessageRecord», или «MessageEntity», или как ее еще называет ORM) определено в DL. в) BL имеет зависимость от DL d) Перед вызовом методов DL, которые не имеют ссылки или не знают о BL, BL должен преобразовать их в объекты BL (например, DbMessageRecord)?

UL:

Main() 
{
    id = 1;
    Message m = BL.GetMessage(id);
    Console.Write (string.Format("{0} to {1} recipients...", m.From, m.To.Count));
}

BL:

static class MessageService 
{ 
    public static Message GetMessage(id)
    {
        DbMessageRecord message = DLManager.GetMessage(id);
        DbMessageAddressRecord[] messageAddresses = DLManager.GetMessageAddresses(id);

        return MapMessage(message, 
    }

    protected static Message MapMessage(DbMessageRecord dbMessage. DbMessageAddressRecord[] dbAddresses)
    {
        Message m = new Message(dbMessage.From);
        foreach(DbMessageAddressRecord dbAddressRecord in dbAddresses){
        m.To.Add(new MessageAddress (dbAddressRecord.Name, dbAddressRecord.Address);
    }
}

DL:

static class MessageManager 
{
    public static DbMessageRecord GetMessage(id);
    public static DbMessageAddressRecord  GetMessageAddresses(id);
}

Вопросы: а) Очевидно, что это много работы, рано или поздно. б) Больше ошибок в) медленнее d) Так как BL теперь зависит от DL и ссылается на классы в DL (например, DbMessageRecord), кажется, что, поскольку они определены ORM, вы не можете разорвать одного поставщика и заменить его другим, делает все упражнение бессмысленным ... может также использовать классы ORM на всем протяжении BL. e) Или ... требуется другая сборка между BL и DL, и требуется другое отображение, чтобы оставить BL независимым от базовых классов DL.

Жаль, что я не мог бы задать вопросы яснее ... но я действительно просто потерян в этот момент. Любая помощь будет принята с благодарностью.

Ответы [ 5 ]

2 голосов
/ 29 мая 2009

Понятие, которое вам, по-видимому, не хватает, - это IoC / DI (то есть инверсия управления / инъекции зависимостей). Вместо использования статических методов каждый из ваших слоев должен зависеть только от интерфейса следующего слоя, с фактическим экземпляром, внедренным в конструктор. Вы можете называть свой DL репозиторием, провайдером или кем-то еще, если это чистая абстракция базового механизма персистентности.

Что касается объектов, которые представляют сущности (грубо сопоставленные с таблицами), я настоятельно рекомендую не иметь двух наборов объектов (один для конкретной базы данных, а другой - нет). Это нормально для них, чтобы на них ссылались все три уровня, пока они являются POCO (они не должны знать, что они сохраняются) или даже DTO (чистые структуры без какого-либо поведения). Создание DTO лучше подходит для вашей концепции BL, однако я предпочитаю, чтобы моя бизнес-логика распространялась на мои доменные объекты («стиль ООП»), а не на представление BL («стиль Microsoft»).

Не уверен насчет Llblgen, но NHibernate + любой IoC, такой как SpringFramework.NET или Windsor, предоставляют довольно чистую модель, которая поддерживает это.

2 голосов
/ 29 мая 2009

это немного повсюду и напоминает мне о моих первых набегах в orm и DDD. Я лично использую основные доменные объекты, объекты сообщений, обработчики сообщений и репозитории. Поэтому мой пользовательский интерфейс отправляет сообщение обработчику, который, в свою очередь, гидратирует мои объекты через репозитории и выполняет бизнес-логику в этом объекте домена. Я использую NHibernate для моего доступа к данным и FluentNHibernate для типизированной привязки, а не вялый дурацкий .hbm config.

Таким образом, обмен сообщениями - это все, что передается между моим пользовательским интерфейсом и моими обработчиками, и все BL находятся в домене.

Я знаю, что мог бы раскрыться для наказания за мои объяснения, если неясно, буду ли я защищаться позже.

Лично я не большой поклонник объектов, генерируемых кодом.

Я должен продолжать добавлять к этому ответу. Старайтесь думать о ваших сообщениях как о команде, а не как о сущности данных, представляющей вашу базу данных. Я приведу пример одного из моих простых классов и решения по инфраструктуре, которое мне очень помогло, и я не могу взять кредит на:

[Serializable]
public class AddMediaCategoryRequest : IRequest<AddMediaCategoryResponse>
{
    private readonly Guid _parentCategory;
    private readonly string _label;
    private readonly string _description;

    public AddMediaCategoryRequest(Guid parentCategory, string label, string description)
    {
        _parentCategory = parentCategory;
        _description = description;
        _label = label;
    }

    public string Description
    {
        get { return _description; }
    }

    public string Label
    {
        get { return _label; }
    }

    public Guid ParentCategory
    {
        get { return _parentCategory; }
    }
}

[Serializable]
public class AddMediaCategoryResponse : Response 
{
    public Guid ID;
}


public interface IRequest<T> : IRequest where T : Response, new() {}


[Serializable]
public class Response
{
    protected bool _success;
    private string _failureMessage = "This is the default error message.  If a failure has been reported, it should have overwritten this message.";
    private Exception _exception;

    public Response()
    {
        _success = false;
    }

    public Response(bool success)
    {
        _success = success;
    }

    public Response(string failureMessage)
    {
        _failureMessage = failureMessage;
    }

    public Response(string failureMessage, Exception exception)
    {
        _failureMessage = failureMessage;
        _exception = exception;
    }

    public bool Success
    {
        get { return _success; }
    }

    public string FailureMessage
    {
        get { return _failureMessage; }
    }

    public Exception Exception
    {
        get { return _exception; }
    }

    public void Failed(string failureMessage)
    {
        _success = false;
        _failureMessage = failureMessage;
    }

    public void Failed(string failureMessage, Exception exception)
    {
        _success = false;
        _failureMessage = failureMessage;
        _exception = exception;
    }
}


public class AddMediaCategoryRequestHandler : IRequestHandler<AddMediaCategoryRequest,AddMediaCategoryResponse>
{
    private readonly IMediaCategoryRepository _mediaCategoryRepository;
    public AddMediaCategoryRequestHandler(IMediaCategoryRepository mediaCategoryRepository)
    {
        _mediaCategoryRepository = mediaCategoryRepository;
    }

    public AddMediaCategoryResponse HandleRequest(AddMediaCategoryRequest request)
    {
        MediaCategory parentCategory = null;
        MediaCategory mediaCategory = new MediaCategory(request.Description, request.Label,false);
        Guid id = _mediaCategoryRepository.Save(mediaCategory);
        if(request.ParentCategory!=Guid.Empty)
        {
            parentCategory = _mediaCategoryRepository.Get(request.ParentCategory);
            parentCategory.AddCategoryTo(mediaCategory);
        }
        AddMediaCategoryResponse response = new AddMediaCategoryResponse();
        response.ID = id;
        return response;
    }
}

Я знаю, что это продолжается и продолжается, но эта базовая система очень хорошо служила мне за последний год или около того

вы можете видеть, что обработчик, который позволяет объекту домена обрабатывать специфичную для домена логику

1 голос
/ 29 мая 2009

Это, вероятно, слишком косвенный ответ, но в прошлом году я боролся с подобными вопросами в мире Java и нашел Шаблоны архитектуры корпоративных приложений Мартина Фаулера весьма полезными (см. Также его шаблон ). каталог ). Многие из шаблонов имеют дело с теми же проблемами, с которыми вы боретесь. Все они очень абстрактные и помогли мне организовать свое мышление, чтобы увидеть проблему на более высоком уровне.

Я выбрал подход, использующий средство отображения iBatis SQL для инкапсуляции наших взаимодействий с базой данных. (Сопоставитель SQL выводит модель данных языка программирования из таблиц SQL, тогда как ORM, подобный вашей, работает наоборот.) Сопоставитель SQL возвращает списки и иерархии объектов передачи данных, каждый из которых представляет строку некоторого результата запроса. Параметры для запросов (и вставки, обновления, удаления) также передаются как DTO. Слой BL вызывает SQL Mapper (запускает этот запрос, вставляет и т. Д.) И обходит DTO. DTO переходят на уровень представления (UI), где они управляют механизмами расширения шаблонов, которые генерируют представления данных в XHTML, XML и JSON. Таким образом, для нас единственной зависимостью DL, которая поступала в UI, был набор DTO, но они сделали UI намного более упорядоченным, чем передача неупакованных значений полей.

Если вы соедините книгу Фаулера с конкретной помощью, которую могут оказать другие авторы, у вас все будет хорошо. В этой области много инструментов и опыта, поэтому впереди еще много хороших путей.

Редактировать: @Ciel, Вы совершенно правы, экземпляр DTO - это просто POCO (или в моем случае Java POJO). Person DTO может иметь поле first_name «Jim» и так далее. Каждый DTO в основном соответствует строке таблицы базы данных и представляет собой просто набор полей, не более того. Это означает, что он не тесно связан с DL и идеально подходит для перехода к пользовательскому интерфейсу. Фаулер говорит об этом на с. 401 (неплохой первый шаблон для разрезания зубов).

Теперь я не использую ORM, который берет ваши объекты данных и создает базу данных. Я использую маппер SQL, который является очень эффективным и удобным способом упаковки и выполнения запросов к базе данных в SQL. Сначала я разработал свой SQL (мне это очень хорошо знакомо), затем я разработал свои DTO, а затем настроил свою конфигурацию iBatis, чтобы сказать, что «select * from Person, где personid = # personid #» должен вернуть мне список Java Персона ДТО объектов. Я еще не использовал ORM (Hibernate, например, в мире Java), но с одним из них вы сначала создадите свои объекты модели данных, а база данных будет построена из них.

Если ваши объекты модели данных имеют всевозможные надстройки, специфичные для ORM, тогда я понимаю, почему вы должны дважды подумать, прежде чем выставлять их на уровень пользовательского интерфейса. Но там вы можете создать интерфейс C #, который определяет только методы get и set POCO, и использовать его во всех ваших API, не относящихся к DL, и создать класс реализации, в котором есть все специфические для ORM вещи:

interface Person ...

class ORMPerson : Person ...

Затем, если вы позже измените свой ORM, вы можете создать альтернативные реализации POCO:

class NewORMPerson : Person ...

и это повлияет только на код уровня DL, поскольку в вашем коде BL и UI используется Person.

@ Zvolkov (ниже) предлагает поднять этот подход «кодирования к интерфейсам, а не реализациям» до следующего уровня, порекомендовав, что вы можете написать свое приложение таким образом, чтобы весь ваш код использовал объекты Person, и чтобы вы можно использовать инфраструктуру внедрения зависимостей для динамической настройки приложения для создания ORMPersons или NewORMPersons в зависимости от того, какой ORM вы хотите использовать в этот день

0 голосов
/ 29 мая 2009

Только мое мнение, YMMV.

Когда я связываюсь с какой-либо новой технологией, я полагаю, что она должна соответствовать двум критериям, или я трачу свое время. (Или я недостаточно хорошо понимаю.)

  1. Это должно упростить вещи или в худшем случае сделать их не более сложными.

  2. Не должно увеличивать сцепление или уменьшать сцепление.

Похоже, вы чувствуете, что движетесь в противоположном направлении, что, как я знаю, не является намерением ни для LINQ, ни для ORM.

Мое собственное восприятие ценности этого нового материала заключается в том, что он помогает разработчику переместить границу между DL и BL в немного более абстрактную территорию. DL меньше похож на необработанные таблицы и больше похож на объекты. Вот и все. (Я обычно работаю довольно усердно, чтобы сделать это в любом случае с немного более тяжелым SQL и хранимыми процедурами, но я, вероятно, более комфортно с SQL, чем в среднем). Но если LINQ и ORM вам пока не помогают, я бы сказал, продолжайте в том же духе, но это конец туннеля; упрощение и перемещение границы абстракции немного.

0 голосов
/ 29 мая 2009

Попробуйте централизовать весь доступ к данным, используя шаблон репозитория. Что касается ваших сущностей, вы можете попробовать реализовать какой-то слой перевода, который отобразит ваши сущности, чтобы он не сломал ваше приложение. Это просто временно и позволит вам медленно рефакторинг вашего кода.

очевидно, я не знаю всей области вашей кодовой базы, так что рассмотрите боль и выгоду.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...