programing

Symphony 응용프로그램의 독트린 엔티티와 비즈니스 로직

codeshow 2023. 9. 24. 13:11
반응형

Symphony 응용프로그램의 독트린 엔티티와 비즈니스 로직

어떤 아이디어나 피드백도 환영합니다. :)

대규모 Symfony2 애플리케이션에서 Detrine2 엔티티중심으로 비즈니스 로직을 처리하는 방법에 문제가 발생했습니다.(게시글 길이 죄송합니다)

많은 블로그, 요리책 등의 자료를 읽어본 결과 다음과 같은 사실을 알게 되었습니다.

  • 엔티티는 데이터 매핑 지속성("Anemic model")에만 사용될 수 있습니다.
  • 컨트롤러는 가능한 한 슬림해야 합니다.
  • 도메인 모델을 지속성 계층에서 분리해야 함(엔티 관리자 알 수 없음)

네, 전적으로 동의합니다만: 도메인 모델의 복잡한 비즈니스 규칙을 어디서 어떻게 처리합니까?


간단한 예

NAT 도메인 모델:

  • 그룹에서 역할을 사용할 수 있음
  • 역할은 여러 그룹에서 사용할 수 있습니다.
  • 사용자는 여러 역할을 가진 여러 그룹에 속할 수 있습니다.

SQL 지속성 계층에서는 다음과 같은 관계를 모델링할 수 있습니다.

enter image description here

당사의 특정 업무 규정:

  • 사용자역할이 그룹에 연결된 경우에만 그룹역할을 가질 수 있습니다.
  • 역할 R1그룹 G1에서 분리하는 경우 그룹 G1역할 R1과의 모든 사용자 역할 영향을 삭제해야 합니다.

이는 매우 간단한 예이지만, 이러한 비즈니스 규칙을 관리하는 가장 좋은 방법을 알고 싶습니다.


찾은 솔루션

1- 서비스 계층에서의 구현

특정 서비스 클래스를 다음과 같이 사용합니다.

class GroupRoleAffectionService {

  function linkRoleToGroup ($role, $group)
  { 
    //... 
  }

  function unlinkRoleToGroup ($role, $group)
  {
    //business logic to find all invalid UserRoleAffectation with these role and group
    ...

    // BL to remove all found UserRoleAffectation OR to throw exception.
    ...

    // detach role  
    $group->removeRole($role)

    //save all handled entities;
    $em->flush();   
}
  • (+) 클래스당 하나의 서비스 / 비즈니스 규칙당 하나의 서비스
  • (-) API 을 수 .$group->removeRole($role)이 서비스에서 제외됩니다.
  • (-) 대규모 애플리케이션에 서비스 클래스가 너무 많습니까?

2 - 도메인 엔티티 관리자에서 구현

특정 "도메인 엔티티 관리자"에 이러한 비즈니스 로직을 캡슐화하고 모델 공급자를 호출합니다.

class GroupManager {

    function create($name){...}

    function remove($group) {...}

    function store($group){...}

    // ...

    function linkRole($group, $role) {...}

    function unlinkRoleToGroup ($group, $role)
    {

    // ... (as in previous service code)
    }

    function otherBusinessRule($params) {...}
}
  • (+) 모든 비지니스 규칙이 중앙 집중식입니다.
  • (-) API 엔티티가 도메인에 표시되지 않습니다. $group->removeRole($role)을(를) 서비스에서 호출할 수 있습니다...
  • (-) 도메인 관리자가 FAT 관리자가 됩니까?

3 - 가능할 때 리스너 사용

심포니 및/또는 독트린 이벤트 수신기 사용:

class CheckUserRoleAffectationEventSubscriber implements EventSubscriber
{
    // listen when a M2M relation between Group and Role is removed
    public function getSubscribedEvents()
    {
        return array(
            'preRemove'
        );
    }

   public function preRemove(LifecycleEventArgs $event)
   {
    // BL here ...
   }

4 - 엔티티를 확장하여 풍부한 모델 구현

엔티티를 도메인 로직 로트를 캡슐화하는 도메인 모델 클래스의 하위/상위 클래스로 사용합니다.하지만 저는 이 해결책이 더 혼란스러워 보입니다.


보다 깨끗하고 분리되고 테스트 가능한 코드에 초점을 맞추어 이러한 비즈니스 로직을 관리하는 가장 좋은 방법은 무엇입니까?귀하의 피드백과 좋은 사례는 무엇입니까?구체적인 예가 있습니까?

주 리소스:

참조: Sf2: 엔티티 내에서 서비스 사용

제 대답이 도움이 될지도 모르겠네요.이는 단지 다음과 같은 내용을 다루고 있습니다.모델 대 저항 대 컨트롤러 계층을 "분리"하는 방법

당신의 구체적인 질문에는, 여기에 "꼼수"가 있다고 말하고 싶습니다."그룹"이란 무엇입니까?'혼자'?아니면 누군가와 관련이 있을 때?

처음에 모델 클래스는 다음과 같이 나타낼 수 있습니다.

UserManager (service, entry point for all others)

Users
User
Groups
Group
Roles
Role

수 있습니다 답변에서 UserManager 는입니다(을 들어, )을는 안 됩니다).new컨트롤러에서 다음과 같은 작업을 수행할 수 있습니다.

$userManager = $this->get( 'myproject.user.manager' );
$user = $userManager->getUserById( 33 );
$user->whatever();

... ...User, 여러분이 말씀하신 것처럼, 할당할 수도 있고 그렇지 않을 수도 있습니다.

// Using metalanguage similar to C++ to show return datatypes.
User
{
    // Role managing
    Roles getAllRolesTheUserHasInAnyGroup();
    void  addRoleById( Id $roleId, Id $groupId );
    void  removeRoleById( Id $roleId );

    // Group managing
    Groups getGroups();
    void   addGroupById( Id $groupId );
    void   removeGroupById( Id $groupId );
}

저는 단순화하였습니다, 물론 ID로 추가하거나 오브젝트로 추가할 수 있습니다.

하지만 '자연어'로 생각하면...어디 보자...

  1. 앨리스가 사진작가라는 것을 알고 있습니다.
  2. 앨리스의 물건을 받았습니다.
  3. 나는 앨리스에게 그룹에 대해 문의합니다.포토그래퍼들이 있어요
  4. 저는 사진작가들에게 역할에 대해 문의합니다.

자세한 내용은 다음을 참조하십시오.

  1. Alice가 user id=33이고 Photographer 그룹에 속해 있는 것으로 알고 있습니다.
  2. 를 User Manager 에게합니다를 를 User 합니다.$user = $manager->getUserById( 33 );
  3. Alice를 통해 Photographers 그룹에 접속합니다. '$group = $user->getGroupByName('Photographers' );
  4. 그럼 그 그룹의 역할을 보고 싶은데요...어떻게 해야 하나?
    • 옵션 1: $group->getRoles();
    • 옵션 2: $group->getRolesForUser($userId );

두번째는 제가 앨리스를 통해 그룹을 얻었기 때문에 중복되는 것과 같습니다.를(를) 생성할 수 .GroupSpecificToUser됩니다.Group.

게임과 비슷한...게임이란 무엇입니까?일반적으로 '체스'로서의 '게임'?아니면 당신과 내가 어제 시작한 체스의 구체적인 "게임"?

$user->getGroups()그룹별 사용자 지정 개체 컬렉션을 반환합니다.

GroupSpecificToUser extends Group
{
    User getPointOfViewUser()
    Roles getRoles()
}

두 방식을 통해 할 다른 많은 , 즉를 할 수 .이 사용자가 여기서 무엇을 해도 됩니까?그룹 하위 클래스를 쿼리할 수 있습니다.$group->allowedToPost();,$group->allowedToChangeName();,$group->allowedToUploadImage();.

에게 , , 과만 하면 됩니다.$user->getRolesForGroup( $groupId );접근.

모델이 저항 계층이 아님

저는 디자인할 때 주변성을 '잊어버리고' 싶어요.저는 보통 팀과 (또는 개인적인 프로젝트를 위해) 앉아서 코드를 작성하기 전에 4~6시간 동안 생각하며 보냅니다.우리는 txt 문서에 API를 작성합니다.그런 다음 방법을 추가하고 제거하는 등의 작업을 반복합니다.

예를 들어, 가능한 "시작점" API에는 삼각형과 같은 모든 것의 쿼리가 포함될 수 있습니다.

User
    getId()
    getName()
    getAllGroups()                     // Returns all the groups to which the user belongs.
    getAllRoles()                      // Returns the list of roles the user has in any possible group.
    getRolesOfACertainGroup( $group )  // Returns the list of groups for which the user has that specific role.
    getGroupsOfRole( $role )           // Returns all the roles the user has in a specific group.
    addRoleToGroup( $group, $role )
    removeRoleFromGroup( $group, $role )
    removeFromGroup()                  // Probably you want to remove the user from a group without having to loop over all the roles.
    // removeRole() ??                 // Maybe you want (or not) remove all admin privileges to this user, no care of what groups.

Group
    getId()
    getName()
    getAllUsers()
    getAllRoles()
    getAllUsersWithRole( $role )
    getAllRolesOfUser( $user )
    addUserWithRole( $user, $role )
    removeUserWithRole( $user, $role )
    removeUser( $user )                 // Probably you want to be able to remove a user completely instead of doing it role by role.
    // removeRole( $role ) ??           // Probably you don't want to be able to remove all the roles at a time (say, remove all admins, and leave the group without any admin)

Roles
    getId()
    getName()
    getAllUsers()                  // All users that have this role in one or another group.
    getAllGroups()                 // All groups for which any user has this role.
    getAllUsersForGroup( $group )  // All users that have this role in the given group.
    getAllGroupsForUser( $user )   // All groups for which the given user is granted that role
    // Querying redundantly is natural, but maybe "adding this user to this group"
    // from the role object is a bit weird, and we already have the add group
    // to the user and its redundant add user to group.
    // Adding it to here maybe is too much.

이벤트

지적된 기사에서 말한 것처럼, 저는 모델에서도 이벤트를 할 것입니다.

예를 들어, 그룹의 사용자로부터 역할을 제거할 때, "리스너"에서 그것이 마지막 관리자라면 a) 역할의 삭제를 취소할 수 있고, b) 허용하고 관리자 없이 그룹을 떠날 수 있으며, c) 허용하되 그룹의 사용자와 함께 새 관리자를 선택하거나 사용자에게 적합한 정책을 선택할 수 있습니다.

마찬가지로, 사용자는 LinkedIn에서와 같이 50개의 그룹에만 속할 수 있습니다.그런 다음 preAddUserToGroup 이벤트를 던지면 사용자가 그룹 51에 가입하려고 할 때 모든 포수가 이를 금지하는 규칙 집합을 포함할 수 있습니다.

이 "규칙"은 사용자, 그룹 및 역할 클래스 밖에 두고 사용자가 그룹에 가입하거나 그룹을 탈퇴할 수 있는 "규칙"을 포함하는 상위 클래스에 남겨둘 수 있습니다.

저는 다른 답변을 보는 것을 강력히 제안합니다.

도움이 되길 바랍니다!

사비.

장기적인 관점에서 가장 쉽게 유지할 수 있는 솔루션으로 1)을 꼽습니다.솔루션 2는 비대해진 "Manager" 클래스를 이끌고, 결국 더 작은 청크로 분해됩니다.

http://c2.com/cgi/wiki?DontNameClassesObjectManagerHandlerOrData

"대규모 애플리케이션에 서비스 클래스가 너무 많다"고 해서 SRP를 피할 수 있는 이유는 아닙니다.

도메인 언어 측면에서 다음 코드가 유사합니다.

$groupRoleService->removeRoleFromGroup($role, $group);

그리고.

$group->removeRole($role);

또한 설명한 내용을 토대로 그룹에서 역할을 제거/추가하려면 많은 종속성(의존성 반전 원리)이 필요하며, FAT/부유 관리자의 경우에는 어려울 수 있습니다.

솔루션 3)은 1)과 매우 유사합니다. 각 가입자는 실제로 엔티티 관리자에 의해 백그라운드에서 자동으로 트리거되는 서비스이며 간단한 시나리오에서는 작동할 수 있지만, 작업(역할 추가/제거)에 많은 컨텍스트가 필요한 즉시 문제가 발생합니다.어떤 사용자가 동작을 수행했는지, 어떤 페이지에서 동작을 수행했는지, 아니면 다른 유형의 복잡한 유효성 검사를 수행했는지를 나타냅니다.

저는 기업을 인식하는 기업을 선호합니다.독트린은 인프라 문제로 인해 모델을 오염시키지 않으며, 반사 기능을 사용하므로 액세스자를 원하는 대로 자유롭게 수정할 수 있습니다.엔티티 클래스에 남아 있을 수 있는 2가지 "Docrine"은 주석(YML 또는 XML 매핑 덕분에 피할 수 있음)이며,ArrayCollection(. ORM 입니다) 에 있는 입니다.Doctrine/Common.), .

따라서 DDD의 기본 사항을 고수하면서 개체는 도메인 논리를 배치할 수 있는 곳입니다.물론 때로는 이것으로도 충분하지 않을 때가 있습니다. 그러면 인프라스트럭처에 대한 우려가 없는 도메인 서비스, 서비스를 자유롭게 추가할 수 있습니다.

독트린 저장소는 더 중간 영역입니다.초기 저장소 패턴을 고수하지 않고 생성된 메서드를 제거하는 경우에도 엔티티를 쿼리할 수 있는 유일한 방법으로 유지하는 것을 선호합니다.몇 년 전에는 주어진 클래스의 모든 가져오기/저장 작업을 캡슐화하기 위해 관리자 서비스를 추가하는 것이 일반적인 Symfony 방식이었습니다.

제 경험으로는 Symfony 폼 구성 요소에 훨씬 더 많은 문제가 발생할 수 있습니다. 사용 여부는 모르겠습니다.그러면 생성자를 사용자 정의하는 능력이 심각하게 제한됩니다. 그러면 이름이 지정된 생성자를 사용할 수 있습니다. PhpDoc p@deprecated̀주석은 쌍에게 원래 생성자를 사용해서는 안 된다는 시각적 피드백을 제공합니다.

마지막으로 독트린 사건에 지나치게 의존하는 것은 결국 여러분을 물게 할 것입니다.거기에는 기술적인 제약이 너무 많고, 게다가 저는 그것들을 추적하기가 어렵다고 생각합니다.필요한 경우 controller/command에서 발송된 도메인 이벤트를 Symfony 이벤트 발송자에 추가합니다.

엔티티 자체와는 별개로 서비스 계층을 사용하는 것을 고려해 보겠습니다.엔티티 클래스는 데이터 구조와 다른 간단한 계산을 설명해야 합니다.복잡한 규칙은 서비스로 이동합니다.

서비스를 사용하는 한 더 많은 분리된 시스템, 서비스 등을 만들 수 있습니다.의존성 주입의 이점을 활용하고 이벤트(디스패치 및 청취자)를 활용하여 서비스 간의 통신을 수행함으로써 서비스 간의 약한 결합을 유지할 수 있습니다.

저는 제 경험을 바탕으로 그렇게 말합니다.처음에 저는 모든 논리를 엔티티 클래스 안에 넣곤 했습니다(특히 symphony 1.x/docrine 1.x 응용 프로그램을 개발할 때).애플리케이션이 늘어나면 유지보수가 정말 어려워집니다.

저는 개인적인 선호로 단순하게 시작해서 더 많은 비즈니스 규칙이 적용됨에 따라 성장하는 것을 좋아합니다.그렇기 때문에 저는 듣는 사람들의 접근을 더 선호하는 경향이 있습니다.

너는 그냥

  • 비즈니스 규칙이 발전함에 따라 청취자를 추가할 수 있습니다.
  • 각자에게 하나의 책임이 있습니다
  • 그리고 이 청취자들을 독립적으로 더 쉽게 테스트할 수 있습니다.

다음과 같은 단일 서비스 클래스가 있는 경우 많은 조롱/비웃음이 필요합니다.

class SomeService 
{
    function someMethod($argA, $argB)
    {
        // some logic A.
        ... 
        // some logic B.
        ...

        // feature you want to test.
        ...

        // some logic C.
        ...
    }
}

언급URL : https://stackoverflow.com/questions/19154729/doctrine-entities-and-business-logic-in-a-symfony-application

반응형