3

I'm starting a new project which will act as an authorization/authentication server and a simple user management for certain type of users. A user can be a HelpDeskResponsible, GroupManager, Employee, Customer and more. All these groups members will be able to login with the same form on the web interface. Each model will contain different set of data describing them.

My problem is model design. I am pretty sure I need some User entity with all data necessary to login and read roles but I don't know how to associate rest of models with user account to easily fetch info about logged-in user. Another problem is users management - given user account is created I'd need to somehow link it to model of one of types I mentioned above.

Is my concept an over-engineering? Are there any solutions for such problem? Maybe I don't need multiple entities for different account types?

Thanks for any advice.

EDIT: Different permission levels are not the bigest problem here - I want to store different info about user depending on a role he belongs to. Customer will have different dataset than Employee. I'm pretty sure they are different models but I want to save an ability to login with the same login form.

Kuba T
  • 2,893
  • 4
  • 25
  • 30
  • So you have 2 bounded contexts: `Authentication BC` with a `User` aggregate root and `Authorisation BC` with a `Role` AR. What kind of data does the `Role` have? – Constantin Galbenu Apr 01 '17 at 14:35
  • Symfony has built-in ROLES_* for this kind of thing. See http://symfony.com/doc/current/security.html – ehymel Apr 01 '17 at 14:41
  • @ConstantinGALBENU `Customer` role contains a company he belongs to while `Emplyoyee` not. `Company` determines what policies should be applied (e.g. product visibility, different price). `Employee` will be able to register new `Customer`s. Moreover, a `Customer` has detailed contact info. Etc etc. Having such roles I am afraid of implementation details, e.g. joins to multiple tables to fetch reference to user. – Kuba T Apr 01 '17 at 14:51
  • @ehymel Yes I know. But my problem isn't related only to permissions but also additional data that need to be stored for different models. – Kuba T Apr 01 '17 at 14:57

2 Answers2

3

I think the main issue is that your coupling goes backward: in your case the User Management context will have to know about specifics of other Bounded Contexts (BC) while it should be the other way around. Otherwise you will find yourself having to constantly modify your User Management context as more systems are using it.

For instance, the HelpDeskResponsible role is much likely played in a Helpdesk system which lives in it's own BC (or set of BCs). It is that context that should be responsible for modeling and persisting the specifics of a HelpDeskResponsible. The only thing the User Management context should hold is whether or not a user is in a specific role and perhaps some information that is generic to all users (e.g. first name, last name).

enter image description here

Obviously, the above diagram misses the Anti-Corruption layer (service(s) e.g. HelpDeskUserService) which lives in the Help Desk BC and is responsible for abstracting away the coupling with the User Management BC and translating User into HelpDeskResponsible.

plalx
  • 42,889
  • 6
  • 74
  • 90
0

Since your additional entities extend User entity with other additional data, I would probably use Class Table Inheritance.

Class Table Inheritance is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint. Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance.

This way, you will have a base table User with base data (username, password, email, etc.) and then another table for each additional entity which extends User with additional data (e.g. column salary on table Employee).


Define the parent entity

Here we're telling to Doctrine that User is our base class. Then Doctrine use a discriminator column to identify which entity your user belongs to. Of course you need to map all entities you want to extend from User (here I add only Employee).

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
 * @ORM\Table(name="user")
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({"user" = "User", "employee" = "Employee"})
 */
class User {
    // your class..
}

Define the child entity

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\EmployeeRepository")
 * @ORM\Table(name="employee")
 */
class Employee extends User {

    /**
     * @ORM\Column(name="salary", type="float")
     */
    private $salary;

    public function setSalary($salary)
    {
        $this->salary = $salary;
        return $this;
    }

    public function getSalary()
    {
        return $this->salary;
    }

}

Of course Employee class can override parent methods (e.g. getRoles() if you implement UserInterface into User).

Finally (backup your DB and) update your DB schema with php bin/console doctrine:schema:update --force. Doctrine will create the new table with defined data plus a column id which refer to relative User.

So when a normal user logs in, Symfony will load User entity while when an employee logs in, it will load Employee entity.

DrKey
  • 3,365
  • 2
  • 29
  • 46