API REST baseada em função?

27

Estou criando uma API REST para a qual vários usuários com funções diferentes terão acesso aos recursos que ela contém.

Para manter o escopo simples, vamos usar o domínio "aluno / professor / turma":

GET /students é o recurso a acessar.

Os usuários podem ter funções como Aluno e / ou Professor

Os alunos terão acesso apenas aos alunos de suas aulas. Os professores terão acesso aos alunos das aulas que ministram. Alguns usos podem ser um aluno E dar outras aulas também. Eles devem ter acesso aos alunos de suas turmas E aos alunos das turmas que ministram.

Idealmente, quero implementar isso como duas funções - uma por função e, em seguida, "união" se um usuário tiver várias funções.

Minha pergunta é: Qual padrão devo usar para implementar isso?

Externamente

  • Devo dividir minha API por função? GET /teacher/studentse GET /student/studentsisso não parece certo para mim.
  • Mantenha tudo que eu sou um recurso (preferencial)

Internamente

Como deve ser implementado internamente?

  • Todo método deve começar com um switch BIG / se por função?
  • Devo implementar um repositório por função?
  • Existe um padrão de design que me ajudará a conseguir isso?

Como um comentário secundário: estou usando a API Web do ASP.NET e o Entity Framework 6 , mas isso realmente não importa para a implementação conceitual.

Casper Jensen
fonte
3
"esta é uma ótima pergunta, eu gostaria de saber se você veio com uma solução para isso, porque estou tentando fazer algo semelhante. O que eu acho que deveria ser: Primeiro, vamos implementar uma API que retorne todos os dados necessários , cada cliente se conectará não diretamente à API, mas a um proxy que será responsável por filtrar os dados de acordo com as funções desse usuário "
Cleiton

Respostas:

11

Você deve arquitetar a API em torno de recursos, não de funções, por exemplo:

/rest/students

deve ser acessível a qualquer pessoa com uma função que permita que eles vejam os alunos.

Internamente, você está implementando segurança baseada em função. Como você faz isso depende dos detalhes do seu aplicativo, mas digamos que você tenha uma tabela de funções, cada pessoa tenha uma ou mais funções e essas funções determinam o que cada pessoa pode acessar. Você já declarou as regras para acessar os alunos:

  • os alunos podem acessar os alunos nas aulas que frequentam
  • os professores podem acessar os alunos nas aulas que ensinam

Então, quando uma pessoa liga:

/rest/students

você chama um método que acessa os alunos, passando o papel da pessoa. Aqui está algum pseudo-código:

roles = person.roles; //array
students = getStudents( roles );
return students;

e nesse método, você pode obter os alunos para cada função com chamadas separadas, por exemplo:

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
  }
}

Essa é uma ideia muito rudimentar para o que você poderia fazer e não necessariamente atender às suas necessidades específicas, mas deve fornecer uma ideia das peças envolvidas. Se você deseja devolver as aulas com cada aluno listado, esta é uma boa abordagem. Se você quiser apenas os alunos, poderá extraí-los de cada classe e fundi-los em uma coleção de alunos.

Não, você não deve ter um repositório separado por função. Tudo o que a função desempenha é determinar como você obtém os dados e, talvez, o que você pode fazer com os dados (por exemplo, os professores podem inserir as notas dos alunos). Os dados em si são os mesmos.

Quanto aos padrões, essa abordagem está usando o Factory Pattern para abstrair o serviço que obtém dados com base na função. Pode ou não ser apropriado ter serviços separados por função. Eu gosto dessa abordagem porque minimiza a quantidade de código em cada estágio do programa e o torna mais legível do que um switch ou um bloco if.

Robert Munn
fonte
1
Obrigado pela resposta. Acabei fazendo algo parecido com o que você sugeriu. Usando LINQ2SQL (C #), eu poderia passar a consulta para cada "função" e aplicar um local para cada função que o usuário obtivesse. O resultado seria uma instrução sql com uma condição "OR" para cada função à qual o usuário tenha acesso. Se nenhuma função for atribuída a um usuário, simplesmente retornarei Enumarable.Empty () ao chamador.
Casper Jensen
0

Encontre uma caneta e um papel e comece a modelar seu sistema.

Você descobrirá que provavelmente precisa de uma entidade de domínio chamada PERSON. Como ALUNOS e PROFESSOR "é" uma "PESSOA, você pode criar uma entidade abstrata chamada PERSON com atributos genéricos como nome, sobrenome, etc. UM PROFESSOR -> é-uma -> Pessoa. Agora você pode tentar encontrar características para um PROFESSOR que não se aplica a ALUNOS; por exemplo, UM PROFESSOR ensina CLASSE (s) em relação a um ou mais ASSUNTO (s).

A imposição da segurança é considerada um aspecto não funcional do seu aplicativo. É uma preocupação transversal que deve ser tratada fora da sua "lógica de negócios". Como aponta o @Robert Munn, todos os ROLE (s) devem ser mantidos em um só lugar. O uso de funções para limitar o acesso a determinadas funções é bastante granular e o conceito é chamado de controle de acesso baseado em função (RBAC).

Para verificar se um professor deve ou não receber as notas de um aluno, ele deve ser expresso no seu modelo de domínio. Digamos que um professor tenha uma aula sobre programação de assuntos. Você provavelmente expressaria em seu modelo que os alunos freqüentam aulas para diferentes disciplinas. É aqui que a lógica do aplicativo / negócios entra em ação. Essa é a lógica que você pode verificar usando o desenvolvimento orientado a testes.

Você deve dividir seus recursos para tornar seu aplicativo testável e modular.

De qualquer forma, a melhor maneira de realmente mostrar o que quero dizer é mostrá-lo com código :) Aqui está uma página do GitHub: https://github.com/thomasandersen77/role-based-rest-api

Boa sorte :)

Thomas Andersen
fonte
3
seu link foi ...
Cleiton