You should architect the API around resources, not around roles, e.g.:
/rest/students
should be accessible to anyone with a role that allows them to see students.
Internally, you are implementing role-based security. How you go about that depends on the details of your application, but let's say you have a role table, each person has one or more roles, and those roles determine what each person can access. You have already stated the rules for accessing students:
- students can access students in the classes they take
- teachers can access students in the the classes they teach
So when a person calls:
/rest/students
you call a method that accesses students, passing in the role of the person. Here is some pseudo code:
roles = person.roles; //array
students = getStudents( roles );
return students;
and in that method, you could get the students for each role with separate calls, e.g.:
factory = getFactory();
classes= [];
students = [];
for( role in roles ){
service = factory.getService( role );
// implementation details of how you get classes for student/teacher are hidden in the service
classes = classes.merge( service.getClasses( person ) );
// classes[] has class.students[]
// loop on classes and add each student to students, or send back classes with nested students? depends on use case
}
}
That's a very rough idea for what you could do and isn't necessarily going to fit your specific needs, but it should give you a sense of the pieces involved. If you want to return the classes with each student listed, this is a good approach. If you just want the students, you could extract them from each class and merge them into a collection of students.
No, you should not have a separate repository per role. All the role does is determine how you get the data, and maybe what you can do with the data (e.g. Teachers can enter Student grades). The data itself is the same.
As for patterns, this approach is using Factory Pattern to abstract away the service that gets data based on role. It may or may not be appropriate to have separate services by role. I like this approach because it minimizes the amount of code at each stage of the program and makes it more readable than a switch or if block.
I've had the same problem and "solved" it by modelling REST resources differently, e.g.:
/users/1 (contains basic user attributes)
/users/1/email
/users/1/activation
/users/1/address
So I've basically split the larger, complex resource into several smaller ones. Each of these contain somewhat cohesive group of attributes of the original resource which is expected to be processed together.
Each operation on these resources is atomic, even though it may be implemented using several service methods - at least in Spring/Java EE it's not a problem to create larger transaction from several methods which were originally intended to have their own transaction (using REQUIRED transaction propagation). You often still need to do extra validation for this special resource, but it's still quite manageable since the attributes are (supposed to be) cohesive.
This is also good for HATEOAS approach, because your more fine-grained resources convey more information on what you can do with them (instead of having this logic on both client and server because it can't be easily represented in resources).
It's not perfect of course - if UIs is not modelled with these resources in mind (especially data-oriented UIs), it can create some problems - e.g. UI presents big form of all attributes of given resources (and its subresources) and allows you to edit them all and save them at once - this creates illusion of atomicity even though client must call several resource operations (which are themselves atomic but the whole sequence is not atomic).
Also, this split of resources is sometimes not easy or obvious. I do this mainly on resources with complex behaviors/life cycles to manage its complexity.
Best Answer
I think that this is the perfect fit for Bounded Context pattern from Domain Driven Design.
Large models don't scale well, it's better to split them into smaller models (bounded contexts) and explicitly declare their relationships (common entities, interactions between bounded contexts etc.)
In this case you would have two bounded contexts (sales and inventory) with one shared resource (item). Interpretation of item in sales context may be a bit different than in inventory context and that's completely fine.
To use this pattern, you should:
Note on
From REST point of view, resource is not duplicated. One resource can be identified by multiple URIs, each can have different representations (fitting for given bounded context).