0

I am currently using DDD (Domain Driven Design) for a new Zend Framework 2 project. Everything works fine but I do have a question regarding the application services.

I understood that application services are located at the application layer and are kind of the entry point to the domain logic. They can access domain services or the repository for example.

I wonder now if it would make sense to implement the application services as controller plugins. In a classical MVC application this controller plugin could handle the results from the called domain services or repositories. Depending on these results they could generate a redirect response or pass data / a form to a ViewModel. If this logic is encapsulated within a plugin my controller only has to call the plugin and return the result of the plugin.

Am I totally wrong about this? Or would you rather keep the logic how to react on results of a domain service or a repository in a controller?

Best Regards,

Ralf

Frille2012
  • 93
  • 7
  • I am not sure what practical distinction you make between domain and application services. I guess you could make plugins for your service classes, but I don't see the advantage so far. I have controller factories, and inject the service(s) the controller needs. Sometimes my services essentially just act like repositories, but I still always define a service, because the application defines a base service, which handles all the domain security. – dualmon Mar 02 '15 at 15:50
  • Well the domain services only handle stuff that belongs into the domain while application services deal as a gateway between application and domain. These application services get repositories or domain services injected and the controller gets the application services injected. – Frille2012 Mar 04 '15 at 12:46

2 Answers2

2

Of course it's kind of subjective and people have strong opinions about things like that... so here's mine:

  1. Controller plugins contain code that's universal to any MVC/REST action and business logic is not universal. Plugins are supposed to facilitate the "controlling" of request/response and not the business logic that's down on the model level. Linking it there would make it less reusable, e.g. for console actions. Also it'd make it less probable to use the business logic with some other framework.
  2. It's awkward to test. Injecting controller plugins as controller class constructor parameters would be kinda redundant, since they are already available from the plugin manager injected into AbstractActionController or AbstractRestfulController. Not having the dependencies injected in a obivious/visible way (like trough contructor method) makes it harder to figure out what the controller class actually depends on. Also since all plugins (AbstractPlugin related) depend on controller instance, switching context from http to console (like for a phpunit test) might get problematic. Also testing logic written/made available as controller plugin would sooner or later escalate to including request/response objects in the tests and that's unnecessary complexity.
  3. It's not intuitive. When I hear plugin I think of something small. Not a full-blown business logic code buried under such inconspicuous name. So when I have little time to debug someones code the last thing I need it to things be out of place like that.

Again I'd like to reiterate, that's just my opinion. I've learned that a lot of patterns can crumble under a weird-enough use case, but the above points made sense to me and my team so far.

guessimtoolate
  • 8,372
  • 2
  • 18
  • 26
  • After further reading I came to the same conclusion. I created the application services as separate command objects. These commands throw exceptions in error cases or return a small result object on success. The controller actions now just handle these exceptions or result objects. Thanks for your thoughts. – Frille2012 Mar 04 '15 at 12:42
0

As an example for my solution here you can see an controller action:

    public function showAction()
    {
        $service = $this->readProductEntityCommand;
        $service->setId($this->params()->fromRoute('id'));

        try {
            $result = $service->execute();
        } catch (ProductException $e) {
            $this->flashMessenger()->addMessage($e->getMessage());

            return $this->redirect()->toRoute('part3/product');
        }

        return new ViewModel(
            array(
                'productEntity' => $result->getData(),
            )
        );
    }

And here is an example of an application service build as an command object

class ReadProductEntityCommand implements CommandInterface
{
    protected $productRepository;

    protected $id;

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

    public function setId($id)
    {
        $this->id = $id;
    }

    public function execute()
    {
        if (is_null($this->id)) {
            throw new ProductIdInvalidException(
                'Produkt ID wurde nicht angegeben.'
            );
        }

        try {
            $product = $this->productRepository->getProduct(
                new ProductIdCriterion(
                    new ProductId($this->id)
                )
            );
        } catch (\Exception $e) {
            throw new ProductNotFoundException(
                'Es konnten kein Produkt gelesen werden.'
            );
        }

        if ($product === false) {
            throw new ProductNotFoundException('Produkt wurde nicht gefunden.');
        }

        $result = new Result();
        $result->setValid(true);
        $result->setData($product);
        $result->setMessage('Produkt wurde gelesen.');

        return $result;
    }
}

Maybe this helps someone in the future.

Frille2012
  • 93
  • 7