symfony-hexagonal-architecture icon indicating copy to clipboard operation
symfony-hexagonal-architecture copied to clipboard

Issue in hexagonal implementation for the controller.

Open ArensMyzyri opened this issue 2 years ago • 1 comments

Hey there. In the UI web controller you are using symfony http response which goes against hexagonal architecture. I would suggest to build a service in infrastructure where you implement the http response and then use its interface on your controller. Example: In Domain create MyHttpResponseInterface and implement it in MySymfonyHttpResponse class (in infrastructure), then use this interface in your controller.

Here an example how I built a controller:

<?php

declare(strict_types=1);

namespace App\Product\Infrastructure\Controller;

use App\Product\Application\Query\GetProductQuery;
use App\Product\Domain\Dto\Product\ProductDto;
use App\Product\Domain\Dto\Uuid;
use App\Product\Domain\Exception\DomainExceptionInterface;
use App\Product\Domain\NormalizerAndSerializer\ModelNormalizerInterface;
use App\Product\Domain\Validator\ModelValidatorInterface;
use App\Product\Domain\Validator\JsonValidatorInterface;
use App\Shared\Domain\Bus\Query\QueryBus;
use App\Shared\Domain\Http\HttpResponseFactoryInterface;
use App\Shared\Domain\Http\HttpResponseInterface;
use App\Shared\Domain\Http\RequestInterface;
use App\Shared\Domain\Http\ResponseDataBuilderInterface;

class ProductController
{
    public function __construct(
        readonly HttpResponseFactoryInterface $httpResponseFactory,
        readonly ResponseDataBuilderInterface $responseDataBuilder,
        readonly QueryBus $queryBus,
        readonly ModelValidatorInterface $modelValidator,
        readonly ModelNormalizerInterface $normalizer,
        readonly JsonValidatorInterface $jsonValidator
    ) {}

    public function getSingle(string $productId, ?string $resourceType = null): HttpResponseInterface
    {
        try {
            $errors = $this->modelValidator->validate([new Uuid($productId)]);

            if ($errors) {
                throw new DomainBadRequestException($errors);
            }

            /** @var ProductDto $product */
            $product = $this->queryBus->ask(new GetProductQuery($productId));

            if (!$product) {
                throw new DomainNotFoundException([]);
            }
            return $this->httpResponseFactory->create(
                $this->responseDataBuilder->build($product, false, ''),
                200,
                $resourceType
            );
        } catch (DomainExceptionInterface $domainException) {
            return $this->httpResponseFactory->create(
                $this->responseDataBuilder->build($domainException->getData(), true, $domainException->getMessage()),
                $domainException->getStatusCode(),
                $resourceType
            );
        }
    }
    }

Let me know what you think :)

ArensMyzyri avatar Dec 21 '22 18:12 ArensMyzyri

The implementation of the service looks like:

<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Http;

use App\Product\Domain\Dto\DtoInterface;
use App\Product\Domain\NormalizerAndSerializer\ModelSerializerServiceInterface;
use App\Shared\Domain\Http\HttpResponseInterface;
use Symfony\Component\HttpFoundation\Response;

class HttpJsonResponse extends Response implements HttpResponseInterface
{
    public function __construct(readonly ModelSerializerServiceInterface $serializer, readonly  DtoInterface $data, readonly  int $statusCode)
    {
        parent::__construct($this->serializer->serializeToJson($data), $statusCode, ['Content-Type' => 'application/json;charset=UTF-8']);
    }
}

As you see only here I use symfony Http Response

ArensMyzyri avatar Dec 21 '22 18:12 ArensMyzyri