PHP & DDD: Как обеспечить, чтобы только служба могла вызывать метод на объекте? - PullRequest
1 голос
/ 08 ноября 2011

Я работаю с моделью предметной области, в которой у меня есть класс резервирования:

class Reservation
{
    public function changeStatus($status) { ... }
}

Поскольку метод changeStatus() должен вызываться только в контексте, в котором отправляются все соответствующие уведомления (электронные письма, ...), я хотел бы ограничить вызов этого метода значением ReservationService:

class ReservationService
{
    public function confirmReservation(Reservation $reservation)
    {
        $reservation->changeStatus(Reservation::STATUS_CONFIRMED);
        // commit changes to the db, send notifications, etc.
    }
}

Поскольку я работаю с PHP, не существует такого понятия, как видимость пакета или классы друзей , поэтому мой метод changeStatus() является просто открытым и поэтому может вызываться из любого места в приложение.

Единственное решение, которое я нашел для этой проблемы, заключается в использовании какой-то двойной отправки :

class Reservation
{
    public function changeStatus(ReservationService $service)
    {
        $status = $service->getReservationStatus($this);
        $this->setStatus($status);
    }

    protected function setStatus($status) { ... }
}

Потенциальные недостатки:

  • Это немного усложняет дизайн
  • Это делает организацию осведомленной о Службе, не уверенной, является ли это недостатком или нет

Ребята, есть ли у вас какие-либо комментарии по поводу вышеупомянутого решения или более удачный дизайн, предлагающий ограничить доступ к этому changeStatus() методу?

Ответы [ 4 ]

4 голосов
/ 08 ноября 2011

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

interface INotifiable {
  public function updated( $reservation );
}

class Reservation {
  public function changeStatus( $status, INotifiable $notifiable ){
    $this->setStatus( $status );
    $notifiable->updated( $this );
  }
}

class EmailNotifier implements INotifiable {
  public function updated( $reservation ){
    $this->sendUpdateEmail( $reservation ); //or whatever
  }
}

При бронировании не нужно ничего знать об услуге. Альтернативой может быть определение событий в Reservation, но это добавляет сложности, которая вам, вероятно, не нужна.

1 голос
/ 30 августа 2012

Вы можете отправлять сообщения от одного доменного объекта другому. Только объекты, способные генерировать определенные сообщения, будут вызывать данный метод. Фрагмент кода ниже. Это решение предназначено для проектов, в которых внедрение зависимостей является своего рода религией, как здесь PHP + DDD .

Служба бронирования получает фабрику сообщений. Фабрика вводится через метод конструктора. Объекты, у которых нет этой фабрики, не могут выдавать подобные сообщения. (Конечно, вы должны ограничивать создание объектов фабриками.)

class Domain_ReservationService
{

    private $changeStatusRequestFactory;

    public function __construct(
        Message_Factory_ChangeStatusRequest $changeStatusRequestFactory
    ) {
        $this->changeStatusRequestFactory = $changeStatusRequestFactory;
    }

    public function confirmReservation(Domain_Reservation $reservation) {

        $changeStatusRequest = $changeStatusRequestFactory->make(
            Reservation::STATUS_CONFIRMED
        );

        $reservation->changeStatus($changeStatusRequest);
        // commit changes to the db, send notifications, etc.

    }

}

Объект Reservation проверяет содержимое сообщения и решает, что делать.

class Domain_Reservation
{

    public function changeStatus(
        Message_Item_ChangeStatusRequest $changeStatusRequest
    ) {
        $satus = $changeStatusRequest->getStatus();
        ...
    }

}

Объект сообщения является объектом значения DDD. (Иногда это действует как стратегия.)

class Message_Item_ChangeStatusRequest
{
    private $status;

    public function __construct( $status ) {
        $this->$status = $status;
    }

    public function getStatus() {
        return $this->$status;
    }

}

Эта фабрика производит сообщения.

class Message_Factory_ChangeStatusRequest
{

    public function make($status) {
        return new Message_Item_ChangeStatusRequest ($status);
    }

}

Все доменные объекты создаются этой фабрикой слоев.

class Domain_Factory
{

    public function makeReservationService() {
        return new Domain_ReservationService(
            new Message_Factory_ChangeStatusRequest()
        );
    }

    public function makeReservation() {
        return new Domain_Reservation();
    }

}

Указанные выше классы могут использоваться в вашем приложении следующим образом.

$factory = new Domain_Factory();
$reservationService = $factory->makeReservationService();
$reservation = $factory->makeReservation();
$reservationService->confirmReservation($reservation);

Но я не понимаю, почему вы не хотите использовать $ booking-> beConfirmed () вместо передачи констант статуса.

0 голосов
/ 05 августа 2016

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

Пример рабочего процесса:

  1. A PlaceReservationCommand отправляется на ReservationContext, который обрабатывает его и публикует ReservationWasPlacedEvent, на который подписан ReservationProcessManager.
  2. ReservationProcessManager получает ReservationWasPlacedEvent и подтверждает, что он может перейти к следующему шагу. Затем я отправляю NotfiyCustomerAboutReservationCommand на NotificationContext. Теперь он слушает ReservationNotificationFailedEvent и ReservationNotificationSucceededEvent.
  3. Теперь ReservationProcessManager отправляет ConfirmReservationCommand на ReservationContext только тогда, когда он получил ReservationNotificationSucceededEvent.

Хитрость в том, что в Reservation нет поля состояния. ProcessManager отвечает за отслеживание состояния этой бизнес-транзакции. Скорее всего, есть ReservationProcess Aggregate, который содержит такие статусы, как ReservationProcess::INITIATED, ReservationProcess::CUSTOMER_NOTIFICATION_REQUESTED, ReservationProcess::CUSTOMER_NOTIFIED, ReservationProcess::CONFIRMATION_REQUESTED и ReservationProcess::CONFIRMED. Последнее состояние указывает конечное состояние, которое помечает процесс как done .

0 голосов
/ 08 ноября 2011

Одной из вещей, которую приняли платформы Symfony2 и FLOW3, является пометка их стабильного общедоступного API комментарием аннотации @api.

Хотя это не совсем то, что вы ищете, это близко. Он документирует части вашего API, на которые могут положиться пользователи. Кроме того, ваша сущность не должна знать об услуге, избегая злой круговой зависимости.

Пример:

class Request
{
    /**
     * Gets the Session.
     *
     * @return Session|null The session
     *
     * @api
     */
    public function getSession()
    {
        return $this->session;
    }
}
...