سؤال

I'm building a REST API for which several users with different roles will have access to the resources it contains.

To keep the scope simple let's take the "student/teacher/class" domain:

GET /students is the resource to access.

Users might have roles like Student and/or Teacher

Students will only have access to students of their classes. Teachers will have access to students of the classes they teach. Some uses may be a student AND teach other classes too. They must have access to the students of their classes AND the students of the classes they teach.

Ideally I want to implement this as two functions - one per role and then "union" if a user have multiple roles.

My question is: Which pattern should I use for implementing this?

Externally

  • Should I split up my API per role? GET /teacher/students and GET /student/students It doesn't seems right to me.
  • Keep it all I'm one resource (preferred)

Internally

How should it be implemented internally?

  • Should every method start with a BIG switch/if per role?
  • Should I implement a repository per role?
  • Is there a design pattern that will help me in achieving this?

As a side comment: I'm using ASP.NET Web API and Entity Framework 6, but it really doesn't matter for the conceptual implementation.

هل كانت مفيدة؟

المحلول

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.

نصائح أخرى

Find a pen and a paper and start modelling your system.

You will find that you probably need a domain entity called PERSON. Since both STUDENTS and TEACHER "is-a" PERSON, you could create an abstract entity called PERSON with generic attributes like firstname, lastname, etc. A TEACHER -> is-a -> Person. Now you can try to find characteristics for a TEACHER that doesn't apply to STUDENTS; e.g. A TEACHER teaches CLASS(es) regarding one or more SUBJECT(s).

Enforcing security is considered a non-functional aspect of your application. It is a cross-cutting concern that should be handled outside of your "business logic". As @Robert Munn points out, the ROLE(s) should all be maintained in one place. Using roles to limit access to certain functions is rather coarse-grained, and the concept is called role-based access control (RBAC).

To verify whether or not a teacher should be allowed to see a students grades, should to be expressed in your domain model. Say a teacher has a class on the subject programming. You would probably express in your model that students attends classes for different subjects. This is where the application/business logic kicks in. This is logic that you can verify using test-driven development.

You should split your resources to make your application testable and modular.

Anyway, the best way to really show what I mean is to show it with code :) Here is a GitHub page: https://github.com/thomasandersen77/role-based-rest-api

Good luck :)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى softwareengineering.stackexchange
scroll top