Symfony应用程序中的原则实体和业务逻辑

时间:2022-02-12 00:32:46

Any ideas / feedback are welcome :)

任何想法/反馈都是受欢迎的:

I run into a problem in how to handle business logic around my Doctrine2 entities in a big Symfony2 application. (Sorry for the post length)

在一个大的Symfony2应用程序中,我遇到了一个如何处理我的理论实体的业务逻辑的问题。(抱歉邮件长度)

After reading many blogs, cookbook and others ressources, I find that :

在阅读了许多博客、食谱和其他资源后,我发现:

  • Entities might be used only for data mapping persistence ("anemic model"),
  • 实体可能仅用于数据映射持久性(“贫血模型”),
  • Controllers must be the more slim possible,
  • 控制器必须尽可能的精简,
  • Domain models must be decoupled from persistence layer (entity do not know entity manager)
  • 域模型必须与持久性层(实体不知道实体管理器)解耦

Ok, I'm totally agree with it, but : where and how handle complex bussiness rules on domain models ?

好吧,我完全同意,但是:在哪里以及如何处理域模型上的复杂业务规则?


A simple example

OUR DOMAIN MODELS :

我们的域模型:

  • a Group can use Roles
  • 一个组可以使用角色
  • a Role can be used by different Groups
  • 角色可以被不同的组使用
  • a User can belong to many Groups with many Roles,
  • 用户可以属于多个具有多个角色的组,

In a SQL persistence layer, we could modelize these relations as :

在SQL持久性层中,我们可以将这些关系表示为:

Symfony应用程序中的原则实体和业务逻辑

OUR SPECIFIC BUSINESS RULES :

我们的具体业务规则:

  • User can have Roles in Groups only if Roles is attached to the Group.
  • 只有当角色附加到组中时,用户才能在组中拥有角色。
  • If we detach a Role R1 from a Group G1, all UserRoleAffectation with the Group G1 and Role R1 must be deleted
  • 如果我们从G1组中分离一个角色R1,则必须删除组G1和角色R1的所有userroleaffecation。

This is a very simple example, but i'd like to kown the best way(s) to manage these business rules.

这是一个非常简单的例子,但是我想知道管理这些业务规则的最佳方式。


Solutions found

1- Implementation in Service Layer

1-服务层的实现

Use a specific Service class as :

使用特定的服务类作为:

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();   
}
  • (+) one service per class / per business rule
  • (+)每个类/每个业务规则提供一个服务
  • (-) API entities is not representating to domain : it's possible to call $group->removeRole($role) out from this service.
  • (-) API实体不代表域:可以从该服务调用$group->removeRole($role)。
  • (-) Too many service classes in a big application ?
  • 在一个大的应用程序中有太多的服务类?

2 - Implementation in Domain entity Managers

2 -域实体管理器的实现。

Encapsulate these Business Logic in specific "domain entities manager", also call Model Providers :

将这些业务逻辑封装在特定的“域实体管理器”中,也调用模型提供程序:

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) {...}
}
  • (+) all businness rules are centralized
  • (+)所有商业规则都是集中的
  • (-) API entities is not representating to domain : it's possible to call $group->removeRole($role) out from service...
  • (-) API实体不代表域:可以从服务中调用$group->removeRole($role)…
  • (-) Domain Managers becomes FAT managers ?
  • (-)领域经理变成胖经理?

3 - Use Listeners when possible

3 -尽可能使用监听器

Use symfony and/or Doctrine event listeners :

使用symfony和/或学说事件监听器:

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 - Implement Rich Models by extending entities

4 -通过扩展实体实现富模型

Use Entities as sub/parent class of Domain Models classes, which encapsulate lot of Domain logic. But this solutions seems more confused for me.

使用实体作为域模型类的子类/父类,它封装了许多域逻辑。但这个解决方案对我来说似乎更令人困惑。


For you, what is the best way(s) to manage this business logic, focusing on the more clean, decoupled, testable code ? Your feedback and good practices ? Have you concrete examples ?

对于您来说,管理这种业务逻辑的最佳方法是什么?你的反馈和良好实践?你有具体的例子吗?

Main Ressources :

主要资源:

5 个解决方案

#1


3  

I find solution 1) as the easiest one to maintain from longer perspective. Solution 2 leads bloated "Manager" class which will eventually be broken down into smaller chunks.

我发现解决方案1)是最容易从长远角度维护的。解决方案2导致臃肿的“管理器”类最终被分解成更小的块。

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

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

"Too many service classes in a big application" is not a reason to avoid SRP.

“大型应用程序中的服务类太多”不是避免SRP的原因。

In terms of Domain Language, I find the following code similar:

在领域语言方面,我发现以下代码类似:

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

and

$group->removeRole($role);

Also from what you described, removing/adding role from group requires many dependencies (dependency inversion principle) and that could be hard with a FAT/bloated manager.

同样,从您所描述的内容中,从组中删除/添加角色需要许多依赖关系(依赖倒置原则),对于臃肿的管理器来说,这可能很难实现。

Solution 3) looks very similar to 1) - each subscriber is actually service automatically triggered in background by Entity Manager and in simpler scenarios it can work, but troubles will arise as soon the action (adding/removing role) will require a lot of context eg. which user performed the action, from which page or any other type of complex validation.

解决方案3)看起来非常类似于1)——每个订阅服务器实际上是由实体管理器在后台自动触发的服务,在更简单的场景中,它可以工作,但是一旦操作(添加/删除角色)将需要大量的上下文,例如。哪个用户执行了操作,从哪个页面或任何其他类型的复杂验证。

#2


5  

See here: Sf2 : using a service inside an entity

参见这里:Sf2:在实体内部使用服务。

Maybe my answer here helps. It just addresses that: How to "decouple" model vs persistance vs controller layers.

也许我的答案对你有帮助。它只是解决了:如何“解耦”模型与持久化模型与控制器层。

In your specific question, I would say that there is a "trick" here... what is a "group"? It "alone"? or it when it relates to somebody?

在你的具体问题中,我想说这里有一个“诡计”……“组”是什么?“单独”吗?还是和某人有关?

Initially your Model classes probably could look like this:

最初,您的模型类可能是这样的:

UserManager (service, entry point for all others)

Users
User
Groups
Group
Roles
Role

UserManager would have methods for getting the model objects (as said in that answer, you should never do a new). In a controller, you could do this:

UserManager将具有获取模型对象的方法(如该答案中所述,您永远不应该做一个新的)。在控制器中,你可以这样做:

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

Then... User, as you say, can have roles, that can be assigned or not.

然后……正如您所说,用户可以有角色,可以分配,也可以不分配。

// 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 );
}

I have simplified, of course you could add by Id, add by Object, etc.

我已经化简了,当然你可以按Id添加,按对象添加等等。

But when you think this in "natural language"... let's see...

但是当你用“自然语言”来思考这个问题的时候……让我们看看…

  1. I know Alice belongs to a Photographers.
  2. 我知道爱丽丝属于摄影师。
  3. I get Alice object.
  4. 我得到爱丽丝对象。
  5. I query Alice about the groups. I get the group Photographers.
  6. 我查询Alice关于组的信息。我找了组摄影师。
  7. I query Photographers about the roles.
  8. 我询问摄影师的角色。

See more in detail:

看到更多的细节:

  1. I know Alice is user id=33 and she is in the Photographer's group.
  2. 我知道Alice是user id=33,她属于拍照者组。
  3. I request Alice to the UserManager via $user = $manager->getUserById( 33 );
  4. 我通过$user = $manager->getUserById(33)向UserManager请求。
  5. I acces the group Photographers thru Alice, maybe with `$group = $user->getGroupByName( 'Photographers' );
  6. 我用“$group = $user->getGroupByName(‘拍照者’)来处理组拍照者。
  7. I then would like to see the group's roles... What should I do?
    • Option 1: $group->getRoles();
    • 选项1:$组- >将getRoles();
    • Option 2: $group->getRolesForUser( $userId );
    • 选项2:$group->getRolesForUser($userId);
  8. 然后我想看看小组的角色……我应该做什么?选项1:$组- >将getRoles();选项2:$group->getRolesForUser($userId);

The second is like redundant, as I got the group thru Alice. You can create a new class GroupSpecificToUser which inherits from Group.

第二个是多余的,因为我通过Alice得到了这个组。您可以创建一个继承自组的新类groupspecitouser。

Similar to a game... what is a game? The "game" as the "chess" in general? Or the specific "game" of "chess" that you and me started yesterday?

类似于一个游戏…什么是游戏?一般来说,“游戏”是“国际象棋”吗?或者你和我昨天开始的“象棋”的具体“游戏”?

In this case $user->getGroups() would return a collection of GroupSpecificToUser objects.

在本例中,$user->getGroups()将返回一组groupspecific对象。

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

This second approach will allow you to encapsulate there many other things that will appear sooner or later: Is this user allowed to do something here? you can just query the group subclass: $group->allowedToPost();, $group->allowedToChangeName();, $group->allowedToUploadImage();, etc.

第二种方法将允许您将迟早会出现的许多其他内容封装在其中:这个用户是否允许在这里做一些事情?您只需查询组子类:$group->allowedToPost();, $group->allowedToChangeName(); $group->allowedToUploadImage()等。

In any case, you can avoid creating taht weird class and just ask the user about this information, like a $user->getRolesForGroup( $groupId ); approach.

无论如何,您可以避免创建taht怪异的类,并向用户询问这个信息,比如$user->getRolesForGroup($groupId);的方法。

Model is not persistance layer

模型不是持久化层

I like to 'forget' about the peristance when designing. I usually sit with my team (or with myself, for personal projects) and spend 4 or 6 hours just thinking before writing any line of code. We write an API in a txt doc. Then iterate on it adding, removing methods, etc.

我喜欢在设计时“忘记”自己的经历。我通常和我的团队(或者我自己,做个人项目)坐在一起,花四六个小时思考,然后再写任何一行代码。我们在txt文档中编写一个API。然后对它进行迭代,添加、删除方法等。

A possible "starting point" API for your example could contain queries of anything, like a triangle:

您的示例的一个可能的“起点”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.

Events

事件

As said in the pointed article, I would also throw events in the model,

正如前面提到的,我也会在模型中加入事件,

For example, when removing a role from a user in a group, I could detect in a "listener" that if that was the last administrator, I can a) cancel the deletion of the role, b) allow it and leave the group without administrator, c) allow it but choose a new admin from with the users in the group, etc or whatever policy is suitable for you.

例如,当删除一个角色从用户在一组,我可以检测的“听众”,如果是最后一个管理员,我可以取消删除的角色,b)使其离开该集团没有管理员,c)允许,但选择一个新的管理员与用户的组,等等之类的政策是适合你。

The same way, maybe a user can only belong to 50 groups (as in LinkedIn). You can then just throw a preAddUserToGroup event and any catcher could contain the ruleset of forbidding that when the user wants to join group 51.

同样,一个用户可能只能属于50个组(比如LinkedIn)。然后,您可以抛出preAddUserToGroup事件,任何捕手都可以包含禁止用户加入51组的规则。

That "rule" can clearly leave outside the User, Group and Role class and leave in a higher level class that contains the "rules" by which users can join or leave groups.

这个“规则”可以明显地离开用户、组和角色类,而留在包含用户可以加入或离开组的“规则”的高级类中。

I strongly suggest to see the other answer.

我强烈建议看看另一个答案。

Hope to help!

希望帮助!

Xavi.

哈维。

#3


2  

As a personal preference, I like to start simple and grow as more business rules are applied. As such I tend to favour the listeners approach better.

作为个人偏好,我喜欢从简单开始,随着应用更多的业务规则而成长。因此,我倾向于更倾向于听众。

You just

你只是

  • add more listeners as business rules evolve,
  • 随着业务规则的发展,添加更多的侦听器,
  • each having a single responsibility,
  • 每个人都有自己的责任,
  • and you can test these listeners independently easier.
  • 你可以更容易地独立地测试这些监听器。

Something that would require lots of mocks/stubs if you have a single service class such as:

如果您只有一个服务类,例如:

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

        // feature you want to test.
        ...

        // some logic C.
        ...
    }
}

#4


0  

I'm in favour of business-aware entities. Doctrine goes a long way not to pollute your model with infrastructure concerns ; it uses reflection so you are free to modify accessors as you want. The 2 "Doctrine" things that may remain in your entity classes are annotations (you can avoid thanks to YML mapping), and the ArrayCollection. This is a library outside of Doctrine ORM (̀Doctrine/Common), so no issues there.

我支持有商业意识的实体。教条主义走了很长一段路,不污染你的模型和基础设施问题;它使用反射,所以您可以随意修改访问器。可能保留在实体类中的两个“Doctrine”内容是注解(由于YML映射,您可以避免使用注释)和ArrayCollection。这是一个图书馆以外的教义ORM(̀教义/普通),所以没有问题。

So, sticking to the basics of DDD, entities are really the place to put your domain logic. Of course, sometimes this is not enough, then you are free to add domain services, services without infrastructure concerns.

因此,坚持DDD的基础,实体是放置域逻辑的地方。当然,有时这还不够,那么您可以*地添加域服务、不涉及基础设施的服务。

Doctrine repositories are more middle-ground: I prefer to keep those as the only way to query for entities, event if they are not sticking to the initial repository pattern and I would rather remove the generated methods. Adding manager service to encapsulate all fetch/save operations of a given class was a common Symfony practice some years ago, I don't quite like it.

原则存储库是更为中庸的:我宁愿将它们作为查询实体的唯一方式,如果实体不遵循初始存储库模式,我宁愿删除生成的方法。添加管理器服务来封装给定类的所有获取/保存操作是几年前常见的Symfony实践,我不太喜欢它。

In my experience, you may come with far more issues with Symfony form component, I don't know if you use it. They will serisouly limit your ability to customize the constructor, then you may rather use named constructors. Adding PhpDoc @deprecated̀ tag wil give your pairs some visual feedback they should not sue the original constructor.

根据我的经验,您可能会遇到更多关于Symfony表单组件的问题,我不知道您是否使用它。它们将严重限制您自定义构造函数的能力,然后您可以使用命名构造函数。添加PhpDoc @deprecated̀标签会让你对一些视觉反馈他们不应该起诉原构造函数。

Last but not least, relying too much on Doctrine events will eventually bite you. They are too many technical limitations there, plus I find those hard to keep track of. When needed, I add domain events dispatched from the controller/command to Symfony event dispatcher.

最后但并非最不重要的是,过分依赖教条事件最终会让你吃不消。它们在技术上有太多的限制,而且我发现很难跟踪它们。当需要时,我添加从控制器/命令发送到Symfony事件分派器的域事件。

#5


0  

I would consider using a service layer apart from the entities itself. Entities classes should describe the data structures and eventually some other simple calculations. Complex rules go to services.

我将考虑在实体本身之外使用服务层。实体类应该描述数据结构,并最终描述其他一些简单的计算。复杂的规则适用于服务。

As long you use services you can create more decoupled systems, services and so on. You can take the advantage of dependency injection and utilize events (dispatchers and listeners) to do the communication between the services keeping them weakly coupled.

只要您使用服务,就可以创建更加分离的系统、服务等等。您可以利用依赖项注入的优势,并利用事件(分派器和侦听器)在保持弱耦合的服务之间进行通信。

I say that on basis of my own experience. In the beginning I used to put all the logic inside the entities classes (specially when I developed symfony 1.x/doctrine 1.x applications). As long the applications grew they got really hard to maintain.

我是根据我自己的经验说的。一开始,我将所有的逻辑放在实体类中(特别是在开发symfony 1时)。x /原则1。x应用程序)。随着应用程序的发展,它们变得很难维护。

#1


3  

I find solution 1) as the easiest one to maintain from longer perspective. Solution 2 leads bloated "Manager" class which will eventually be broken down into smaller chunks.

我发现解决方案1)是最容易从长远角度维护的。解决方案2导致臃肿的“管理器”类最终被分解成更小的块。

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

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

"Too many service classes in a big application" is not a reason to avoid SRP.

“大型应用程序中的服务类太多”不是避免SRP的原因。

In terms of Domain Language, I find the following code similar:

在领域语言方面,我发现以下代码类似:

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

and

$group->removeRole($role);

Also from what you described, removing/adding role from group requires many dependencies (dependency inversion principle) and that could be hard with a FAT/bloated manager.

同样,从您所描述的内容中,从组中删除/添加角色需要许多依赖关系(依赖倒置原则),对于臃肿的管理器来说,这可能很难实现。

Solution 3) looks very similar to 1) - each subscriber is actually service automatically triggered in background by Entity Manager and in simpler scenarios it can work, but troubles will arise as soon the action (adding/removing role) will require a lot of context eg. which user performed the action, from which page or any other type of complex validation.

解决方案3)看起来非常类似于1)——每个订阅服务器实际上是由实体管理器在后台自动触发的服务,在更简单的场景中,它可以工作,但是一旦操作(添加/删除角色)将需要大量的上下文,例如。哪个用户执行了操作,从哪个页面或任何其他类型的复杂验证。

#2


5  

See here: Sf2 : using a service inside an entity

参见这里:Sf2:在实体内部使用服务。

Maybe my answer here helps. It just addresses that: How to "decouple" model vs persistance vs controller layers.

也许我的答案对你有帮助。它只是解决了:如何“解耦”模型与持久化模型与控制器层。

In your specific question, I would say that there is a "trick" here... what is a "group"? It "alone"? or it when it relates to somebody?

在你的具体问题中,我想说这里有一个“诡计”……“组”是什么?“单独”吗?还是和某人有关?

Initially your Model classes probably could look like this:

最初,您的模型类可能是这样的:

UserManager (service, entry point for all others)

Users
User
Groups
Group
Roles
Role

UserManager would have methods for getting the model objects (as said in that answer, you should never do a new). In a controller, you could do this:

UserManager将具有获取模型对象的方法(如该答案中所述,您永远不应该做一个新的)。在控制器中,你可以这样做:

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

Then... User, as you say, can have roles, that can be assigned or not.

然后……正如您所说,用户可以有角色,可以分配,也可以不分配。

// 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 );
}

I have simplified, of course you could add by Id, add by Object, etc.

我已经化简了,当然你可以按Id添加,按对象添加等等。

But when you think this in "natural language"... let's see...

但是当你用“自然语言”来思考这个问题的时候……让我们看看…

  1. I know Alice belongs to a Photographers.
  2. 我知道爱丽丝属于摄影师。
  3. I get Alice object.
  4. 我得到爱丽丝对象。
  5. I query Alice about the groups. I get the group Photographers.
  6. 我查询Alice关于组的信息。我找了组摄影师。
  7. I query Photographers about the roles.
  8. 我询问摄影师的角色。

See more in detail:

看到更多的细节:

  1. I know Alice is user id=33 and she is in the Photographer's group.
  2. 我知道Alice是user id=33,她属于拍照者组。
  3. I request Alice to the UserManager via $user = $manager->getUserById( 33 );
  4. 我通过$user = $manager->getUserById(33)向UserManager请求。
  5. I acces the group Photographers thru Alice, maybe with `$group = $user->getGroupByName( 'Photographers' );
  6. 我用“$group = $user->getGroupByName(‘拍照者’)来处理组拍照者。
  7. I then would like to see the group's roles... What should I do?
    • Option 1: $group->getRoles();
    • 选项1:$组- >将getRoles();
    • Option 2: $group->getRolesForUser( $userId );
    • 选项2:$group->getRolesForUser($userId);
  8. 然后我想看看小组的角色……我应该做什么?选项1:$组- >将getRoles();选项2:$group->getRolesForUser($userId);

The second is like redundant, as I got the group thru Alice. You can create a new class GroupSpecificToUser which inherits from Group.

第二个是多余的,因为我通过Alice得到了这个组。您可以创建一个继承自组的新类groupspecitouser。

Similar to a game... what is a game? The "game" as the "chess" in general? Or the specific "game" of "chess" that you and me started yesterday?

类似于一个游戏…什么是游戏?一般来说,“游戏”是“国际象棋”吗?或者你和我昨天开始的“象棋”的具体“游戏”?

In this case $user->getGroups() would return a collection of GroupSpecificToUser objects.

在本例中,$user->getGroups()将返回一组groupspecific对象。

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

This second approach will allow you to encapsulate there many other things that will appear sooner or later: Is this user allowed to do something here? you can just query the group subclass: $group->allowedToPost();, $group->allowedToChangeName();, $group->allowedToUploadImage();, etc.

第二种方法将允许您将迟早会出现的许多其他内容封装在其中:这个用户是否允许在这里做一些事情?您只需查询组子类:$group->allowedToPost();, $group->allowedToChangeName(); $group->allowedToUploadImage()等。

In any case, you can avoid creating taht weird class and just ask the user about this information, like a $user->getRolesForGroup( $groupId ); approach.

无论如何,您可以避免创建taht怪异的类,并向用户询问这个信息,比如$user->getRolesForGroup($groupId);的方法。

Model is not persistance layer

模型不是持久化层

I like to 'forget' about the peristance when designing. I usually sit with my team (or with myself, for personal projects) and spend 4 or 6 hours just thinking before writing any line of code. We write an API in a txt doc. Then iterate on it adding, removing methods, etc.

我喜欢在设计时“忘记”自己的经历。我通常和我的团队(或者我自己,做个人项目)坐在一起,花四六个小时思考,然后再写任何一行代码。我们在txt文档中编写一个API。然后对它进行迭代,添加、删除方法等。

A possible "starting point" API for your example could contain queries of anything, like a triangle:

您的示例的一个可能的“起点”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.

Events

事件

As said in the pointed article, I would also throw events in the model,

正如前面提到的,我也会在模型中加入事件,

For example, when removing a role from a user in a group, I could detect in a "listener" that if that was the last administrator, I can a) cancel the deletion of the role, b) allow it and leave the group without administrator, c) allow it but choose a new admin from with the users in the group, etc or whatever policy is suitable for you.

例如,当删除一个角色从用户在一组,我可以检测的“听众”,如果是最后一个管理员,我可以取消删除的角色,b)使其离开该集团没有管理员,c)允许,但选择一个新的管理员与用户的组,等等之类的政策是适合你。

The same way, maybe a user can only belong to 50 groups (as in LinkedIn). You can then just throw a preAddUserToGroup event and any catcher could contain the ruleset of forbidding that when the user wants to join group 51.

同样,一个用户可能只能属于50个组(比如LinkedIn)。然后,您可以抛出preAddUserToGroup事件,任何捕手都可以包含禁止用户加入51组的规则。

That "rule" can clearly leave outside the User, Group and Role class and leave in a higher level class that contains the "rules" by which users can join or leave groups.

这个“规则”可以明显地离开用户、组和角色类,而留在包含用户可以加入或离开组的“规则”的高级类中。

I strongly suggest to see the other answer.

我强烈建议看看另一个答案。

Hope to help!

希望帮助!

Xavi.

哈维。

#3


2  

As a personal preference, I like to start simple and grow as more business rules are applied. As such I tend to favour the listeners approach better.

作为个人偏好,我喜欢从简单开始,随着应用更多的业务规则而成长。因此,我倾向于更倾向于听众。

You just

你只是

  • add more listeners as business rules evolve,
  • 随着业务规则的发展,添加更多的侦听器,
  • each having a single responsibility,
  • 每个人都有自己的责任,
  • and you can test these listeners independently easier.
  • 你可以更容易地独立地测试这些监听器。

Something that would require lots of mocks/stubs if you have a single service class such as:

如果您只有一个服务类,例如:

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

        // feature you want to test.
        ...

        // some logic C.
        ...
    }
}

#4


0  

I'm in favour of business-aware entities. Doctrine goes a long way not to pollute your model with infrastructure concerns ; it uses reflection so you are free to modify accessors as you want. The 2 "Doctrine" things that may remain in your entity classes are annotations (you can avoid thanks to YML mapping), and the ArrayCollection. This is a library outside of Doctrine ORM (̀Doctrine/Common), so no issues there.

我支持有商业意识的实体。教条主义走了很长一段路,不污染你的模型和基础设施问题;它使用反射,所以您可以随意修改访问器。可能保留在实体类中的两个“Doctrine”内容是注解(由于YML映射,您可以避免使用注释)和ArrayCollection。这是一个图书馆以外的教义ORM(̀教义/普通),所以没有问题。

So, sticking to the basics of DDD, entities are really the place to put your domain logic. Of course, sometimes this is not enough, then you are free to add domain services, services without infrastructure concerns.

因此,坚持DDD的基础,实体是放置域逻辑的地方。当然,有时这还不够,那么您可以*地添加域服务、不涉及基础设施的服务。

Doctrine repositories are more middle-ground: I prefer to keep those as the only way to query for entities, event if they are not sticking to the initial repository pattern and I would rather remove the generated methods. Adding manager service to encapsulate all fetch/save operations of a given class was a common Symfony practice some years ago, I don't quite like it.

原则存储库是更为中庸的:我宁愿将它们作为查询实体的唯一方式,如果实体不遵循初始存储库模式,我宁愿删除生成的方法。添加管理器服务来封装给定类的所有获取/保存操作是几年前常见的Symfony实践,我不太喜欢它。

In my experience, you may come with far more issues with Symfony form component, I don't know if you use it. They will serisouly limit your ability to customize the constructor, then you may rather use named constructors. Adding PhpDoc @deprecated̀ tag wil give your pairs some visual feedback they should not sue the original constructor.

根据我的经验,您可能会遇到更多关于Symfony表单组件的问题,我不知道您是否使用它。它们将严重限制您自定义构造函数的能力,然后您可以使用命名构造函数。添加PhpDoc @deprecated̀标签会让你对一些视觉反馈他们不应该起诉原构造函数。

Last but not least, relying too much on Doctrine events will eventually bite you. They are too many technical limitations there, plus I find those hard to keep track of. When needed, I add domain events dispatched from the controller/command to Symfony event dispatcher.

最后但并非最不重要的是,过分依赖教条事件最终会让你吃不消。它们在技术上有太多的限制,而且我发现很难跟踪它们。当需要时,我添加从控制器/命令发送到Symfony事件分派器的域事件。

#5


0  

I would consider using a service layer apart from the entities itself. Entities classes should describe the data structures and eventually some other simple calculations. Complex rules go to services.

我将考虑在实体本身之外使用服务层。实体类应该描述数据结构,并最终描述其他一些简单的计算。复杂的规则适用于服务。

As long you use services you can create more decoupled systems, services and so on. You can take the advantage of dependency injection and utilize events (dispatchers and listeners) to do the communication between the services keeping them weakly coupled.

只要您使用服务,就可以创建更加分离的系统、服务等等。您可以利用依赖项注入的优势,并利用事件(分派器和侦听器)在保持弱耦合的服务之间进行通信。

I say that on basis of my own experience. In the beginning I used to put all the logic inside the entities classes (specially when I developed symfony 1.x/doctrine 1.x applications). As long the applications grew they got really hard to maintain.

我是根据我自己的经验说的。一开始,我将所有的逻辑放在实体类中(特别是在开发symfony 1时)。x /原则1。x应用程序)。随着应用程序的发展,它们变得很难维护。