0
votes

Lets say I have a REST resource like:

/companies/{companyId}/departments/{departmentId}/employees/{employeeId}

and entity classes where CompanyEntity has List<DepartementEntity> and DepartmentEntity has List<EmployeeEntity>. All IDs are unique.

So now somebody is calling

GET /companies/{companyId}/departments/{departmentId}/employees/{employeeId}

What is a good way to find the employee with {employeeId} in Spring Data JPA / Hibernate?

  1. Way:

    employeeRepository.findOne(employeeId);

Pro: Just 1 query

Contra: companyId and departmentId are unused. They can even be random. And this is rather GET /employee/{id} but I want to keep the nested resources with company and department.

Also I would like to access company object to check if the one who is asking is in the same company as the employee.

  1. Way:

    company = companyRepository.findOne(companyId);

    for(DepartmentEntity department : company.getDepartments()) {

     if(department.getId() == departmentId) {
       for(EmployeeEntity employee : department.getEmployees()) {
         if(employee.getId() == employeeId)
         return employee;
       }
     }
    

    }

Pro: companyId and departementId are considered

Contra: Many queries if you use lazy loading

Thanks.

1
How about if (employee.getDepartment().getId() != departmentId || employee.getDepartment().getCompany().getId() != companyId) { throw ... }JB Nizet
Currently I do not have these back references from employee to department and department to company. But maybe it is time to integrate them. Thanks for the point.NoobieNoob
No, avoid bidirectional associations as much as possible, they usually cause trouble in the long run as the blur the API space and are notoriously hard to get right. Also, never let URI structures dictate your code. If ther employee identifiers are globally unique why would you make it so hard to the client to look one up (it needing to provide all those apparently unnecessary details)?Oliver Drotbohm
Oliver, Thanks. I want to deliver the employee information only if the one who queries and the target employee are in the same company. So finding the target is simply: employeeRepository.findOne(employeeId). But what is a good way to check if the companies are the same? Note that there is department between company and employee and no bidirectional references. I thought I could use the company id in URI then.NoobieNoob

1 Answers

0
votes

Just reading through the questions and comments, a few points:

  • Taking JB Nizet's suggestion more broadly, I think the idea is to use the most straightforward lookup available, and then validate. The validations protect your data from unauthorized access, and also ensure that relational query semantics hold. i.e. the user would not expect an employee record to be returned if it were not in the specified department; nor if the department were not valid inside the specified company.

    So it makes sense (in your implementation, not necessarily in the API) to provide an easy way to get the department for an employee, and the company for the department, so you can perform these validations. Alternatively, each container can have a hashed collection of contained IDs, so you can easily determine if a given employee ID is within the department.employees collection, and same with department in company.departments.

  • It's not unusual, and it's often helpful, to have nested collections like this. Relational Endpoints is one name for this API design pattern. But I agree with Oliver Gierke that you should provide users a direct path to a known entity. Different ways to factor this into your design:

    • You can keep the nested resources, and add canonical /departments and /employees collections. In results returned from the nested resources, you can include a canonical link, as the pattern suggests. So GET /companies/123/departments/456/employees/789 could return a representation of employee, along with a canonical link (in the header or body, depending on your wire format) to /employees/789, and the client can use this for subsequent access to the employee record.
    • Taking this a step further, you could decide only to support one level of nesting. So GET /companies/{companyId}/departments returns a collection of hyperlinks to departments within the company; where each hyperlink is to the canonical URL, e.g. /departments/456. No need for further levels of nesting, because employees, and everything else you need to know about the department, are available through the canonical resource.