Моделирование контекста покупок

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

Я разделил свое приложение на несколько ограниченных контекстов (каталог, покупки, заказ), и приведенный ниже пример основан на покупках. Я также пытаюсь использовать CQRS (таким образом, команда AddProductToCart).

Мои бизнес-требования следующие:

  • Клиент должен иметь возможность добавить товар в корзину
  • Цена на добавленный продукт должна быть индивидуальной для пользователя и основываться на нескольких факторах: (глобальные скидки, скидки для пользователей и страна покупателя (в некоторых странах базовые цены снижены).

Я распознал следующих актеров (обратите внимание на комментарии к методам и классам):

/**
 * A customer (Aggregate Root) having an unique cart
 */
class Customer
{
    /**
     * @var int
     */
    private $customerId;

    /**
     * @var string
     */
    private $country;

    /**
     * @var Cart
     */
    private $cart;

    /**
     * @var PriceProvider
     */
    private $priceProvider;

    /**
     * Adds product to customers cart with user-specific price
     *
     * @param $productId
     * @return CartLine
     */
    public function addProductToCart($productId)
    {
        $price = $this->priceProvider->priceForProduct($productId, $this->customerId, $this->country);
        return $this->cart->addLine($productId, $price);
    }
}

/**
 * Simple CartLine object for persisting purposes
 */
class CartLine
{
    public $productId;
    public $price;
    public $cartId;

    function __construct($cartId, $productId, $price)
    {
        $this->cartId    = $cartId;
        $this->price     = $price;
        $this->productId = $productId;
    }
}

class Cart
{
    private $cartId;

    public function addLine($productId, $price)
    {
        return new CartLine($this->cartId, $productId, $price);
    }
}

/**
 * Provides price for specific country
 */
class PriceProvider
{
    public function priceForProduct($productId, $userId, $country)
    {
        // Logic for determining product price for customer
        // Based on available global discounts, user discounts and country
    }
}

/**
 * Command for adding product to cart
 */
class AddProductToCart
{
    public $customerId;
    public $productId;
}

/**
 * An application service to bind everything together
 */
class CustomerService
{
    public function addProductToCart(AddProductToCart $command)
    {
        /** @var Customer $customer */
        $customer = $this->customerRepository->customerOfId($command->customerId);
        $cartLine = $customer->addProductToCart($command->productId);

        $this->cartLineRepository->save($cartLine);
    }
}

Это правильный подход? Нарушаю ли я здесь какие-либо принципы DDD? Могу ли я что-то улучшить?


person acid    schedule 10.08.2014    source источник
comment
Вам также нужно событие: AddProductToCartCommand--›ProductAddedToCartEvent   -  person MeTitus    schedule 11.08.2014
comment
Просто из любопытства, предназначено ли это приложение для обучения или для производства?   -  person Sudarshan    schedule 11.08.2014
comment
@Sudarshan, это может оказаться небольшим производством. ;)   -  person acid    schedule 11.08.2014
comment
Почему вы спрашиваете? Должен ли я что-то изменить?   -  person acid    schedule 11.08.2014


Ответы (2)


CQRS обычно включает обработчик команд.
Команда связана с "императивным" глаголом.
CustomerService слишком широкое и поэтому не отражает цель "команды".

Я бы заменил CustomerService на AddProductToCart:

class AddProductToCart
{
    public function handle(AddProductToCartCommand $command) {
      ...
    }
}

Кстати, разделив все ваши различные функции на соответствующие команды, вы сможете сделать несколько версий одной конкретной команды, если вам это нужно.

person Mik378    schedule 10.08.2014
comment
Вы правы, я должен был использовать обработчик там. Я предполагаю, что этот обработчик должен быть частью уровня домена, а не уровня приложения? - person acid; 11.08.2014
comment
слой приложений, он по-прежнему похож на службу приложений - person Mik378; 11.08.2014
comment
@acid Вы должны прочитать это: code.google.com /p/ddd-cqrs-sample/wiki/ Существует даже полный пример (в коде) CQRS, связанный с обработчиками команд. - person Mik378; 11.08.2014

Итак, в разреженном порядке (и частично в зависимости от языка программирования):

  • Ваш совокупный корень должен быть явно помечен (например, с использованием интерфейса IAggregateRoot), поэтому, если ваш язык поддерживает общий, вы можете реализовать только репозиторий совокупного корня.
  • Ваши конструкторы всегда должны быть закрытыми, вместо этого используйте factory. Используйте конструктор только для инициализации. В будущем правило сборки Customer может усложниться, и фабрика предоставит вам гибкость.
  • Инвариантная логика — это доменная логика. Логика, которая зависит от варианта использования, — это логика приложения (т. е. PriceProvider не связана с логикой предметной области).
person BAD_SEED    schedule 12.08.2014
comment
Каким образом определение того, какие правила применять к какому продукту для определения его цены, не является логикой предметной области? Может быть, он не должен находиться в этом AR (а если нет, то где?), но для меня это определенно уровень предметной области. - person acid; 13.08.2014