I am modelling user management domain where I have to address domain activities such as registration, login, role management etc.. I have come up with below aggregates.
When user registration is approved, I have to add an entry into users table and then persist his roles
Users <<root>> ----> User Roles (child entity)
New roles can be created by choosing appropriate privileges
Roles <<root>> ----> Role Privileges (child entity)
Privilege master table
Privileges <<root>>
My questions is, in the User Roles (child entity) of Users aggregate, can I have role_id which links to Roles aggregate root?
If I have to persist user roles, the business invariant is to make sure the roles are valid roles and hence I have to validate role_id against role master. So in the same transaction, can read data from another aggregate? Please note that, these aggregates are in the same microservice.
Thanks for the help.
Define "links".
It's perfectly OK for an aggregate to refer/link to another aggregate root by a stable identifier (e.g. a string ID). You can then use that ID to request another aggregate (e.g. from a repository). With that other aggregate you can perform reads, but should never perform updates.
Remember that aggregates define consistency and transactional boundaries. The aggregate you get need not be the latest version of that aggregate: it's a version of that aggregate from some point before you requested it (hopefully it's the latest version as of when you requested it, but that might not always be the case) and it cannot reflect changes since you requested it.
However, if your reference/link is through something more direct, then those consistency/transactional boundaries are violated, so that's generally not allowed (or at least not a good idea, as you're giving up other benefits of DDD).
Related
after some time, I am still struggling to model my domain properly. Let me briefly introduce simplified background.
It is product monitoring SaaS. User needs to obtain a membership which defines his abilities and limits, let's call him a Member. Member can subscribe to products in order to keep track on product changes, and therefore being notified about it. Member can also create a group to which he can add subscribed products in order to customize notification behavior - "hey, in case of these products, notify me only if price drops more than 20%". Simply as that.
At first, I created three aggregates.
ProductAggregate
MembershipAggregate
GroupAggregate
Even though my use case is fairly simply, I can't figure what is a proper way of modeling that.
Member can subscribe to products. Does "subscribe" method belongs to Membership or Product aggregate? Membership can exist without subscriptions, so is Product.
Member can create group – I would say it belongs to GroupAggregate, but membership limits (i.e. member can create max. 3 groups) needs to be checked. Group has no idea about that, so we need to load membership aggregate to check if it is possible.
As you see above, both cases require knowledge about membership limits, so it would be natural to place it all in membership aggregate. On the other hand, pretty much every action in the system will depend on user membership limits and thus everything would have to go through that aggregate – which is obviously bad.
The only solution I came up with is to build membership with method like "canCreateGroup()" etc. and retrieve that aggregate in command handler (application layer). So CreateGroupCommandHandler would do:
Load membership aggregate, execute canCreateGroup
Load group aggregate, execute CreateGroup
However, this way everything related to membership would be checked in application layer (command handlers) and I believe it is a domain responsibility, so it would be wrong as well.
It appears that there's a requirement that no member ever breach their limits. In that case, every operation that could possibly breach the limits has to run through the membership aggregate. There's no way around that.
You can model the process of creating a group as its own process (with state, which enables resumption) in the domain, as in the saga pattern. For a given member ID and group ID, the create group saga:
attempts to add the group to the member's set of pending groups (the member validates that the group would not breach limits; note that this command has to be idempotent)
if that succeeds, records that the member has approved creation of the group
creates the group
if that succeeds, records that the group has been created
moves the group from the member's set of pending groups to the member's set of active groups
if that succeeds, record that the group creation process has completed
The reservation process means that the failure mode would be a group which never gets created stays in pending and eventually prevents a member from creating more groups. This situation can be detected by subscribing to the events from members (you seem to be event sourcing, judging from the tagging) and canceling (or perhaps resuming, depending on the interval) hung group creation attempts.
I'm creating a micro services for example one for user management i.e (roles, credentials, rights, menus etc) related and one for bank account-details, now i have a scenario to getting user roles detail and rights detail from db, is it a good practice to repeat columns of database user management db i.e.(roles, rights) in bank-account db as per requirement or duplicate data in bank-account db ?
Or no need to duplicate data in bank-account db and send a separate call to get user data first from user management db ?
please suggest a best possibility
Waqas,
You're in microservices environment. There is a well-known Domain Driven Design (DDD) with one of a key pattern of Bounded Contexts. That means you should try to avoid mixing the contexts and duplicate the user information in bank-account db (it might be inevitable some time, but I suppose not in your case).
Therefore, it's fine that you have to call user management service in order to gain the required information about your users.
I agree with #Stepan Tsybulski. Also, I suggest you reduce the need for a bounded context to depend on the other to the minimum possible. So, duplication of data is the best option here. However, you do not have to have roles and rights in the Bank Account context / DB. I'd put in the Bank Account context only what's necessary in that context: User details (such as names, birthdates, etc.) + user id.
You get the roles and rights from the session. You manage roles in the User Management context only. It's good for security and consistency, plus it's the reason for using Contexts.
There are many ways to approach a problem, but that's how I'm doing it.
I have read about creating and responsibilities of aggregates and I have doubts how to correctly implement them. Assume that we have context within there are 2 entities. One is a Company and the second one is a User. Business rules are in the Company entity that means this should become aggregate root. To the Company we can assign ony 3 users and we can not assign User when Comapny has status "blocked". User has also possibility to login using emial and password. With that in mind every action on User entity should bo invoked thru the Aggregate root and User should not have it's own Repository. How to make login action on User when we can not do it directly without Company root? We can not call User out of the aggregate. How to find User with provided email and password? Fetching all Aggregates and iterating over their Users is inefficient and I think it's not a good idea.
Thank you for help.
I think that user should belong to another BC (that manages authentication and authorization). In your Company BC, you have to get the user from the authentication and authorization BC. You have to integrate both BCs with a context mapping pattern, where authentication and authorization BC is upstream, and Company BC is downstream.
Authentication usually isn't part of a domain (in 99% of all use cases), just part of the infrastructure.
As such Users shouldn't ever appear within a bounded context. In the real business world, there are no users neither, only People, Persons, Employees, Managers or Contacts etc.
So for logging concerns you have our users with username + password which serve as authentication. These users have an id (numerical, string or guid).
Your Employee or Persons entity/aggregate (or what ever you named it depends on your domain, the exact term depends from company to company - the ubiquitous language) then only contains the data which belongs to the person (but not identificaton related information).
You can then connect employees to users (either by having the employee id be the id of the users used for login, an extra field or via a 1:1 or 1:n lookup table.
This way you can easily delete a user (the login) without deleting the Employee entity, because in real world scenarios you can't easily just delete business data (i.e. imagine deleting a user removes the recipient on every invoice or CRM data, no one would ever know this person worked there in the past).
In the systems, there may be data that is restricted in nature.
Sometimes access to specific entities should be easily restricted or granted based on user or group membership.
What is the best way to implement this in the microservice architecture?
#1
Should access control, managing permissions etc. be the responsibility of the microserive itself? Developers will have to implement access control, store, and update permissions for every service. Seems like not very robust and error-prone approach.
#2
Create dedicated microservice handling permission management? This service will be called by other microserives to check access permissions for each entity and filtering entities before returning results. Centralized permissions storage and management is an advantage but microservice will have to make a call to "Permission Service" for each entity to check access rights what may have a negative influence on performance. And developers still have to integrate access checks into their services what leaves space for an error.
#3
Make access control responsibility of the API Gateway or Service Mesh. It is possible to think of an implementation that will automatically filter responses of all services. But in the case when the microservice returns list of entities permissions should be checked for each entity. Still a potential performance problem.
Example
Consider the following synthetic example.
Healthcare system dealing with test results, X-Ray images etc. Health information is very sensitive and should not be disclosed.
Test results should be available only to:
patient
doctor
laboratory
Attending doctor may send the patient to another specialist. A new doctor should have access to test results too. So access can be granted dynamically.
So each entity (e.g. test results, X-Ray image) has a set of rules what users and groups are allowed to access it.
Imagine there is a microservice called "Test Results Service" dealing with test results. Should it be responsible for access control, manage permissions etc.? Or permissions management should be extracted to separate microservice?
Healthcare system may also handle visits to a doctor. Information about patient's visit to the doctor should be available to:
patient
doctor
clinic receptionist
This is the example of a different entity type that requires entity level access restriction based on user or group membership.
It is easy to imagine even more examples when entity level access control is required.
I came to the following generic solution.
ACL security model is used. Each object in the system has associated set of permissions. Permissions defines who and what actions can perform on the object.
Microservices are responsible for entity-level authorization and filter objects in responses based on permissions of the objects.
Central Access Control Service is responsible for the creation, update, and deletion of permissions for all objects in the system. Access Control Service database is the primary store of objects' permissions.
Permissions stored in microservices databases are synchronized with Access Control Service database using event-carried state transfer. Every time, permissions are changed an event is sent to the message broker. Microservices can subscribe to these events to synchronize permissions.
API Gateway can be used as the additional protection layer. API Gateway can call Access Control Service directly (RPC) to check response objects' permissions or load recently revoked permissions.
Design features:
A way to uniquely identify each object in the system is required (e.g. UUID).
Permissions synchronization in microservices are eventual consistent. In case of partitioning between message broker and microservice permissions will not be synchronized. It may be a problem with revocation of the permissions. The solution to this problem is a separate topic.
Looks like security is a part of business logic here. In both examples.
Then security could be a part of data scheme.
For example,
Patient can see his tests:
select * from test_result where patient_id=*patient_id*
Doctor can see all test from his medical department:
select * from test_result where branch_id=*doctor_branch*
I believe that to have separate MS for access control is a really bad idea and could lead serious performance problems. Just imagine situation that somebody with zero entity access tries to fetch all entities each time :) You will always need to handle larger result sets than actually needed.
Firstly, this is very bad idea to have a separate (per microservice) security model. It should be single always cross-cutting all application, because it can lead to a hell with access management, permissions granting and mapping between entities in different microservices.
In second, I assume that you are wrong with understanding how to organize microservices..? You should dedicate the principle of splitting functionality into microservices: by features, by domain, etc. Look at Single Responsibility, DDD and other approaches which helps you to achieve clear behavior of your MS.
So, in best case, you should have to:
Choose right security model ABAC or RBAC - there are a lot of other options, but looking at your example I guess the ABAC is the best one
Create separate MS for access management - the main responsibility of this MS is a CRUD and assignment of groups/roles/permissions/attributes to the people accounts.
Create separate MS for providing only permitted health information.
In third, how it works?:
With ABAC you can setup hierarchical roles/permissions (based on groups/attributes) - it helps you to resolve a delegation path of who is permitted to the data
Setup authorization (via auth-MS) and store the list of permissions (in session, cookies, etc)
Check access for a given user for a needed data in health-info-MS. Here we have several options how to do this:
If you use memory-grids (hazelcast, coherence), you can easily create filters with predicates based on security attributes.
If you're using SQL (hibernate, plain SQL, etc.) you should generate queries to return only permitted data - add security specific criteria to the where clause
Few more details about SQL queries with security check in where: before the SQL execution (if hibernate & spring is easy to do with spring-method-auth hook) you should resolve all permissions assigned to a user - you can do this with call to auth-MS.
Example
We created CRUD permissions for TestResult entity - VIEW, EDIT, DELETE.
The role DOCTOR can see any TestResults - so, it has VIEW permission
The role PATIENT can see only his/her TestResults
So, you create a business rules which provide the correct where clause for each business role (DOCTOR, PATIENT, LAB, etc.) and at the end the SQL request would be like:
For patient who has assigned VIEW permission:
select * from test_result where id=*patient_id* and 1=1
For patient who hasn't assigned VIEW permission:
select * from test_result where id=*patient_id* and 1!=1
NOTE: In business rules we can add 1=1 or 1!=1 to permit/restrict query result
I'm using Odoo v8 and need a means to prevent certain users (either (preferably) in a sales team or in a user group) from accessing certain contacts (assigned to a different sales team) or the contacts at all.
I tried to achieve this via Record rules but don't seem to get this to work.
Users and user roles are critical points concerning internal security in OpenERP. OpenERP provides several security mechanisms concerning user roles, all implemented in the OpenERP Server. They are implemented in the lowest server level, which is the ORM engine. OpenERP distinguishes three different concepts:
user: a person identified by its login and password. Note that all employees of a company are not necessarily OpenERP users; an user is somebody who accesses the application.
group: a group of users that has some access rights. A group gives its access rights to the users that belong to the group. Ex: Sales Manager, Accountant, etc.
security rule: a rule that defines the access rights a given group grants to its users. Security rules are attached to a given resource, for example the Invoice model.
Security rules are attached to groups. Users are assigned to several groups. This gives users the rights that are attached to their groups. Therefore controlling user roles is done by managing user groups and adding or modifying security rules attached to those groups.
Users
Users represent physical persons using OpenERP. They are identified with a login and a password,they use OpenERP, they can edit their own preferences, … By default, a user has no access right. The more we assign groups to the user, the more he or she gets rights to perform some actions. A user may belong to several groups.
User groups
The groups determine the access rights to the different resources. A user may belong to several groups. If he belongs to several groups, we always use the group with the highest rights for a selected resource. A group can inherit all the rights from another group
Rights
Security rules are attached to groups. You can assign several security rules at the group level, each rule being of one of the following types :
- access rights are global rights on an object,
- record rules are records access filters,
- fields access right,
- workflow transition rules are operations rights.
You can also define rules that are global, i.e. they are applied to all users, indiscriminately of the groups they belong to. For example, the multi-company rules are global; a user can only see invoices of the companies he or she belongs to.
Concerning configuration, it is difficult to have default generic configurations that suit all applications. Therefore, like SAP, OpenERP is by default pre-configured with best-practices.
Access rights
Access rights are rules that define the access a user can have on a particular object . Those global rights are defined per document type or model. Rights follow the CRUD model: create, read (search), update (write), delete. For example, you can define rules on invoice creation. By default, adding a right to an object gives the right to all records of that specific object.
Record rules
When accessing an object, records are filtered based on record rules. Record rules or access filters are therefore filters that limits records of an object a group can access. A record rule is a condition that each record must satisfy to be created, read, updated (written) or deleted. Records that do not meet the constraints are filtered.
For example, you can create a rule to limit a group in such a way that users of that group will see business opportunities in which he or she is flagged as the salesman. The rule can be salesman = connected_user. With that rule, only records respecting the rule will be displayed.
Field access rights
New in version 7.0.
OpenERP now supports real access control at the field level, not just on the view side. Previously it was already possible to set a groups attribute on a <field> element (or in fact most view elements), but with cosmetics effects only: the element was made invisible on the client side, while still perfectly available for read/write access at the RPC level.
As of OpenERP 7.0 the existing behavior is preserved on the view level, but a new groups attribute is available on all model fields, introducing a model-level access control on each field. The syntax is the same as for the view-level attribute:
_columns = {
'secret_key': fields.char('Secret Key', groups="base.group_erp_manager,base.group_system")
}
There is a major difference with the view-level groups attribute: restricting the access at the model level really means that the field will be completely unavailable for users who do not belong to the authorized groups:
Restricted fields will be completely removed from all related views, not just hidden. This is important to keep in mind because it means the field value will not be available at all on the client side, and thus unavailable e.g. for on_change calls.
Restricted fields will not be returned as part of a call to fields_get() or fields_view_get() This is in order to avoid them appearing in the list of fields available for advanced search filters, for example. This does not prevent getting the list of a model’s fields by querying ir.model.fields directly, which is fine.
Any attempt to read or write directly the value of the restricted fields will result in an AccessError exception.
As a consequence of the previous item, restricted fields will not be available for use within search filters (domains) or anything that would require read or write access.
It is quite possible to set groups attributes for the same field both at the model and view level, even with different values. Both will carry their effect, with the model-level restriction taking precedence and removing the field completely in case of restriction.
Note
The tests related to this feature are in openerp/tests/test_acl.py.
Warning
At the time of writing the implementation of this feature is partial
and does not yet restrict read/write RPC access to the field. The
corresponding test is written already but currently disabled.
You can add a Rule for a group of users. I just made it and it's work fine.