Laravel에서 만든 응용 프로그램과 도메인 (참고로 infra) 의 독립된 모드

17394 단어 PHPLaravel
안녕하십니까?Laravel Advent Calender 2019 8일째 보도.

이 문장에 관하여


2일차@nunulk 씨는 Laravel 모델 클래스를 어디에 배치할지 고민라는 기사를 써서'모드 4: 응용 프로그램과 독립된 도메인'을 소개했습니다. 메리드메를 써 주십시오.마침 본사가 개발한 &A 클라우드 서비스가 이런 설계 방침을 채택하였으니 제가 소개해 드리겠습니다.

왜 그런 디자인을 했을까요?


이렇게 디자인하고 싶은 이유는 두 가지가 있습니다.
  • 데이터베이스에 접근하는 클래스와 논리적인 클래스를 명확하게 구분
  • Laravel의 DI 용기를 이용하여 외부 데이터 원본에 대한 추상적인 접근을 통해 층을 나누어 설계

  • 전통적인 PHP 프레임워크의 MVC에서 공유는view에 의존하지 않는 논리의 배치는 반드시 모델이 되고 모델은 데이터 접근과 논리 두 가지 역할을 한다.그 밖에 여러 모델과 관련된 논리를 작성할 때 어떤 모델에도 논리를 쓰는 것이 적합하지 않은 문제가 발생할 수 있다.
    또한 모델 자체가 모든 데이터를 가지고 있기 때문에 모델의 논리를 다른 종류로 분해하기 어렵기 때문에 모델은 점점 커질 것이다.모델의 논리를 단독 클래스로 전환할 수 없기 때문에 여러 모델 간에 논리를 공유하기 어렵다.
    예를 들어 EC 사이트를 고려할 때 사용자와 상점에 이메일이 있다는 것은 흔히 있는 일이지만 이메일 부분의 논리를 공통화시키는 것은 모델 자체에 논리를 쓰기가 쉽지 않다.
    모델을 영역으로 응용 프로그램 논리에 필요한 단위로 재조합하면 분류를 세분화하여 논리 공유에 편리하게 할 수 있다.
    데이터 소스에 대한 액세스가 추상화되면 EloquentModel에서 QueryBuilder로 변경됩니다.DB에 직접 액세스할지 Cache를 통해 액세스할지 선택할 수 있습니다.화면을 관리하는 데이터만 마스터의 데이터베이스에서 얻어야 하는 등 방문한 데이터 원본을 안전하게 전환할 수 있다는 장점이 있다.

    이것은 최초의 목적에 도달할 것이다.
    개념적인 모델은 도메인으로 명명되고 도메인 클래스라고 부른다.모델은 EloquentModel로서 데이터베이스에 접근하는 편리한 기능으로 포지셔닝됩니다.

    실현 방법


    실현 방법의 예로 사용자만 있는 응용 프로그램을 고려한다.실용성은 없지만...앱은 사용자 응용 프로그램의 인터페이스로서domain은 논리적으로, infra는 데이터 원본에 접근하는 실현을 쓴다.실제 개발된 서비스는 앱의 부분에서 더욱 세밀한 역할을 하는 반군이 이번에는 어떻게 모델을 만드는지 화제가 돼 사랑을 끊었다.

    디렉터리 배포 예

    ./app
    ├── Console
    │   └── Commands
    ├── Exceptions
    ├── Http
    │   ├── Controllers
    │   ├── Middleware
    │   └── Requests
    └── Providers
        └── RepositoryServiceProvider.php
    
    ./domain
    ├── Base
    │   ├── BaseId.php
    │   ├── BaseStringValue.php
    │   └── BaseTime.php
    ├── Common
    │   ├── Email.php
    │   ├── RawPassword.php
    │   └── HashedPassword.php
    └── User
        ├── User.php
        ├── UserDomainService.php
        ├── UserId.php
        ├── UserList.php
        ├── UserMailer.php
        └── UserRepository.php
    
    ./infra
    ├── EloquentModel
    │   └── UserModel.php
    └── EloquentRepository
        ├── Cached
        │    └── UserRepository.php
        └── UserRepository.php
    
    또한namespace를 유효하게 하기 위해composer.설정을 json에 추가하는 중입니다.
    composer.json
    {
        ... 省略 ...
        "autoload": {
            "classmap": [
                "database/seeds",
                "database/factories"
            ],
            "psr-4": {
                "App\\": "app/",
                "Domain\\": "domain/",
                "Infra\\": "infra/"
            }
        },
        ... 省略 ...
    }
    
    
    autoload의 psr-4에서 Domain과 Infra를 지정하고namespace를 추가합니다.

    app


    웹 액세스든 Console 명령이든 앱은 Domain 논리를 호출하여 사용자의 요청에 응답합니다.
    당신은 자료 파일 라이브러리 서비스 제공자에서 인터페이스의 어떤 실현을 설정할 수 있습니다.
    app/Providers/RepositoryServiceProvider.php
    
    <?php
    
    namespace App\Providers;
    
    use Domain\User\UserRepository;
    use Illuminate\Support\ServiceProvider;
    use Infra\EloquentRepository\UserRepository as EloquentUserRepository;
    
    class RepositoryServiceProvider extends ServiceProvider
    {
        public function boot()
        {
    
        }
    
        public function register()
        {
            $this->app->bind(UserRepository::class, EloquentUserRepository::class);
        }
    }
    

    domain


    domain은 논리를 쓰는 곳으로서 그 실현 방법은 infra를 실현하는 것이다.
    domain에는 프레임에 의존하지 않는 순수한 PHP 클래스만 남아 있습니다.베이스에 기본 클래스를 놓는 abstract 클래스, Common에 공동으로 사용하는 클래스를 놓는다.클래스를 이메일, Raw Password, Hashed Password 등 작은 클래스 단위로 나누면 작은 클래스 단위가 전자 우편 주소의 완전성과 Password의 검증 등 논리를 가지게 할 수 있다.사용자 종류가 많은 응용 프로그램은 당연히 이곳의 논리를 사용할 수 있다.
    domain/User/User.php
    <?php
    
    namespace Domain\User;
    
    use Domain\Common\Email;
    use Domain\Common\HashedPassword;
    
    class User {
    
        private $id;
        private $email;
        private $hashedPassword;
    
        public function __construct(
            UserId $id,
            Email $email,
            HashedPassword $hashedPassword
        )
    
        public function checkPassword(Hasher $hasher, RawPassword $rawPassword): bool
        {
            return $this->hashedPassword->check($hasher, $rawPassword);
        }
    }
    
    
    domain/Common/HashedPassword.php
    <?php
    
    namespace Domain\Common;
    
    use Domain\Base\BaseStringValue;
    use Illuminate\Contracts\Hashing\Hasher;
    
    class HashedPassword extends BaseStringValue
    {
        public function check(Hasher $hasher, RawPassword $rawPassword): bool
        {
            return $hasher->check($rawPassword->rawValue(), $this->rawValue());
        }
    }
    
    또한 스토리지 모드를 가져와 데이터 소스에 대한 액세스는 Domain 측면에 인터페이스를 두고 Infra 측면에 설치합니다.
    domain/User/UserRepository.php
    <?php
    
    namespace Domain\User;
    
    use Domain\Common\Email;
    
    interface UserRepository
    {
        public function get(UserId $id): User;
    
        public function getByEmail(Email $email): User;
    }
    

    infra


    infra는 외부 데이터 원본에 접근하는 것을 포함합니다.실현할 모든 클래스는 인터페이스를 구현합니다.
    infra/EloquentModel/UserRepository.php
    <?php
    
    namespace Infra\EloquentRepository;
    
    use Domain\Common\Email;
    use Domain\User\User;
    use Domain\User\UserId;
    use Domain\User\UserRepository as UserRepositoryInterface;
    
    
    class UserRepository implements UserRepositoryInterface
    {
    
        public function get(UserId $id): User
        {
            $model = UserModel::where('id', $id->rawValue())
                ->first();
            if (!$model) {
                throw new NotFoundException();
            }
    
            return $model->toDomain();
        }
    
        public function getByEmail(Email $email): User
        {
            $model = UserModel::where('email', $email->rawValue())->first();
            if (!$model) {
                throw new NotFoundException();
            }
    
            return $model->toDomain();
        }
    }
    
    
    EloquentModel에는 도메인 클래스로 변환하는 함수가 있습니다.
    infra/EloquentModel/User.php
    <?php
    
    namespace Infra\EloquentModel;
    
    use Domain\User\User as UserDomain;
    
    class User {
    
        public function toDomain(): UserDomain;
        {
            return new UserDomain(
                new UserId($this->id),
                new Email($this->email),
                new HashedPassword($this->password),
            );
        }
    }
    
    

    장점과 단점


    제가 이 디자인의 장단점을 소개하겠습니다.

    이점


    당초의 목적에 따라 논리와 데이터 접근을 분리하고 논리의 종류만 만드는 것은 매우 크다.이렇게 하면 너는 논리를 분해하고 사용할 수 있는 정교한 종류를 세울 수 있다.
    또한 층을 나누면 추상적인 데이터 접근이 실현되기 때문에 여러 가지 실현이 있고 상하문에 따라 전환할 수 있으며 안전하게 이동할 수 있다.예를 들어 사용자의 방문에 따라 Cache 접근을 사용하고 싶지만 관리 화면에서 Cache를 사용하지 않는 데이터 접근을 사용하고 싶은 등 수요에 따라 상하문에 따라 자료 파일 라이브러리 서비스 공급업체에서 사용하는 자료 파일 라이브러리의 실현 클래스를 대체할 수 있다.

    결점


    단점은
  • EloquentModel에서 Domain 클래스로 데이터를 매핑하기 어렵습니다
  • EloquentModel은 Dao일 뿐이므로 기능을 활용할 수 없음
  • 내 생각에는 이런 곳이다.
    응용 프로그램이 복잡할수록 Domain 측의 논리가 많을수록 Infra 측의 번거로움이 상대적으로 적기 때문에 저는 이것을 포기할 수 있다고 생각합니다.그리고 솔직히 이런 모드라면 Eloquent를 사용하지 않는다고 할 수 있다.

    끝내다


    Laravel은 규칙이 없는 프레임워크이기 때문에 디렉터리와 층을 스스로 설계해야 합니다. 이에 비해 이렇게 하는 것이 좋습니다. 이런 정보는 매우 적기 때문에 더 많은 사람들이 이 주제를 썼으면 합니다.나도 라벨을 처음 사용했을 때 모색했어.
    이전에 이것과 비슷한 주제인 LT 당시의 자료가 있으니 꼭 보십시오.
    https://speakerdeck.com/kazuhei0108/sabisukontenafalseshi-jian-de-nahuo-yong?slide=23
    디자인 참고서
  • 현장에서 유용한 시스템 설계 원칙 ~ 변경이 가볍고 안전한 대상을 향한 실천 기교
  • 실천 영역 구동 설계
  • 좋은 웹페이지 즐겨찾기