Архитектура приложения C # - EF5 и понимание уровня обслуживания

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

Итак, мы начали использовать EF5 и применять некоторые шаблоны / слои проектирования к нашему приложению.

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

Давайте покажем вам, что у нас есть на данный момент:

  • мы ввели EF (Code First with POCOs) для сопоставления нашей устаревшей базы данных (работает достаточно хорошо)
  • мы создали репозитории для большинства вещей, которые нам нужны на нашем новом уровне данных (конкретные реализации, я не вижу каких-либо преимуществ в отношении разделения проблем с использованием общих репозиториев ..)

Теперь в конкретном случае речь идет о расчете цен на товар - либо путем получения цены напрямую из arcile, либо из группы, в которой находится товар (если цена не указана). Это становится намного сложнее, потому что также задействованы разные прейскуранты (в зависимости от полной стоимости заказа) и в зависимости от клиента, у которого также могут быть специальные цены и т. Д.

Итак, мой главный вопрос: кто отвечает за правильную цену?

Мои мысли таковы: Орден должен знать, из каких предметов он состоит. Эти товары, с другой стороны, должны знать, какова их цена, но заказ не должен знать, как рассчитать цену товара, он должен просто суммировать их стоимость.

Выдержка моего кода на данный момент:

ArticlePrice (POCO, Сопоставления скоро будут заменены на Fluid API)

[Table("artikeldaten_preise")]
public class ArticlePrice : BaseEntity
{
    [Key]
    [Column("id")]
    public int Id { get; set; }

    [Column("einheit")]
    public int UnitId { get; set; }

    [ForeignKey("UnitId")]
    public virtual Unit Unit { get; set; }

    [Column("preisliste")]
    public int PricelistId { get; set; }

    [ForeignKey("PricelistId")]
    public virtual Pricelist Pricelist { get; set; }

    [Column("artikel")]
    public int ArticleId { get; set; }

    [ForeignKey("ArticleId")]
    public virtual Article Article { get; set; }

    public PriceInfo PriceInfo { get; set; }

}

Репозиторий цен на статьи:

   public class ArticlePriceRepository : CarpetFiveRepository
{
    public ArticlePriceRepository(CarpetFiveContext context) : base(context) {}

    public IEnumerable<ArticlePrice> FindByCriteria(ArticlePriceCriteria criteria)
    {
        var prices = from price in DbContext.ArticlePrices
                     where
                         price.PricelistId == criteria.Pricelist.Id
                         && price.ArticleId == criteria.Article.Id
                         && price.UnitId == criteria.Unit.Id
                         && price.Deleted == false
                     select price;

        return prices.ToList();
    }
}

public class ArticlePriceCriteria
{
    public Pricelist Pricelist { get; set; }
    public Article Article { get; set; }
    public Unit Unit { get; set; }

    public ArticlePriceCriteria(Pricelist pricelist, Article article, Unit unit)
    {
        Pricelist = pricelist;
        Article = article;
        Unit = unit;
    }
}

PriceService (действительно ужасно пахнет ...)

public class PriceService
{
    private PricelistRepository _pricelistRepository;
    private ArticlePriceRepository _articlePriceRepository;
    private PriceGroupRepository _priceGroupRepository;

    public PriceService(PricelistRepository pricelistRepository, ArticlePriceRepository articlePriceRepository, PriceGroupRepository priceGroupRepository)
    {
        _pricelistRepository = pricelistRepository;
        _articlePriceRepository = articlePriceRepository;
        _priceGroupRepository = priceGroupRepository;
    }

    public double GetByArticle(Article article, Unit unit, double amount = 1, double orderValue = 0, DateTime dateTime = new DateTime())
    {
        var pricelists = _pricelistRepository.FindByDate(dateTime, orderValue);

        var articlePrices = new List<ArticlePrice>();

        foreach (var list in pricelists)
            articlePrices.AddRange(_articlePriceRepository.FindByCriteria(new ArticlePriceCriteria(list, article, unit)));

        double price = 0;
        double priceDiff = 0;

        foreach (var articlePrice in articlePrices)
        {
            switch (articlePrice.PriceInfo.Type)
            {
                    case PriceTypes.Absolute:
                        price = articlePrice.PriceInfo.Price;
                        break;
                    case PriceTypes.Difference:
                        priceDiff = priceDiff + articlePrice.PriceInfo.Price;
                    break;
            }
        }

        return (price + priceDiff) * amount;
    }

    public double GetByPriceGroup(PriceGroup priceGroup, Unit unit)
    {
        throw new NotImplementedException("not implemented yet");
    }

    //etc. you'll get the point that this approach might be completely WRONG

}

Мои последние вопросы: как правильно смоделировать свою проблему? Верно ли, что я нахожусь на пути к усилению архитектуры своего кода? Как правильно будет выглядеть мой уровень обслуживания? Хотел бы я иметь ArticlePriceService, ArticleGroupPriceService и т. Д.? Но кто бы мог соединить эти части и рассчитать правильную цену? Было бы, например, быть ответственностью OrderItemService, у которого есть метод GetPrice? Но опять же, orderItemService должен знать о других сервисах.

Пожалуйста, попытайтесь предоставить мне возможные решения относительно архитектуры и того, какой объект / уровень что делает.

Не стесняйтесь задавать мне дополнительные вопросы, если вам нужна дополнительная информация!


person bberger    schedule 13.06.2013    source источник
comment
Если бы вы могли уменьшить размер своего вопроса до половины или даже трети исходного вопроса, вы бы получили больше людей, читающих это ...   -  person oleksii    schedule 13.06.2013
comment
Я открыт для предложений, но я действительно не понимаю, как я мог бы сократить его, не убирая важную информацию, касающуюся моих вопросов: /   -  person bberger    schedule 13.06.2013


Ответы (2)


Вы представили простой сценарий, которого может быть достаточно самого репозитория.
У вас есть больше репозиториев?
Ожидаете ли вы роста вашего приложения и увеличения количества используемых репозиториев?

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

Одна из причин использования сервисов может появиться, когда вы хотите получить данные из нескольких разных репозиториев, а затем выполнить какую-то агрегацию / манипуляции с данными.
Уровень сервиса тогда предоставит логику манипуляции, в то время как потребителю сервиса не придется иметь дело с несколькими разными репозиториями.
Вам также следует подумать о ситуациях, когда вы можете захотеть изменить более одного объекта в одной транзакции (Имеется в виду - более одного репозитория) и сохранение изменений в БД только тогда, когда все действия обновления были успешными.
Эта ситуация должна предполагать использование Шаблон единицы работы и, вероятно, завершит использование уровня обслуживания, чтобы обеспечить надлежащее модульное тестирование.

person Liel    schedule 13.06.2013
comment
Что ж, в моем сценарии сама цена рассчитывается из разных источников: она должна учитывать ArticlePrices, ArticleGroupPrices и CustomerPrices, которые поступают из 3 разных репозиториев. Он также должен учитывать, из какого прейскуранта все они поступают, и он должен знать, какой прейскурант преобладает над другим. Меня беспокоит следующее: моя PriceService должна знать обо всех этих факторах и о том, как они влияют на каждый другое .. Также: я бы тогда имел OrderPriceService, который знает о ItemPriceService, который знает о PriceService (Article)? - person bberger; 13.06.2013
comment
Трудно сказать вам, как спроектировать вашу систему. Очень часто репозиторий ДОЛЖЕН извлекать данные, а уровень сервиса выполняет всю логику. Кроме того, как я уже писал, вам следует подумать о возможных сценариях ОБНОВЛЕНИЯ, а не только о сценариях извлечения данных. - person Liel; 13.06.2013
comment
Я не вижу ничего плохого в вашем коде на PriceService. Как вы думаете, почему это неправильно? - person Liel; 13.06.2013
comment
Что мне не нравится в PriceService, так это то, что он должен знать, как рассчитывать цену для нескольких целей. В конце концов, у меня были бы методы для таких вещей, как ..ByArticle, ByOrder, ByGroup и т. Д. Я бы предпочел какой-нибудь метод вроде Order.GetPrice(), который делает что-то вроде Sum(Articles.GetPrice(), DeliveryOptions.GetPrice()) и т. Д. - person bberger; 20.06.2013

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

Мне кажется, ваш сервис должен называться «ShopService» (или что-то подобное). Тогда ваш метод GetByArticle должен называться GetPriceByArticle.

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

Может быть, вы можете спросить себя: «Как называется моя страница или окно, которое взаимодействует с этой службой?» Есть только один или несколько? Если больше, то что у них общего? Это может помочь вам найти хорошее название для вашей услуги и, следовательно, различные методы получения того, что нужно каждому.

Расскажи мне больше. Буду рад помочь.

person fradiv    schedule 03.02.2015