一文教你如何使用 MongoDB 和 HATEOAS 创建 REST Web 服务
作者 | Ion Pascari
译者 | 天道酬勤 责编 | 徐威龙
封图| CSDN 下载于视觉中国
客户端-服务器:以这样的方式实施服务:将用户界面关注点(客户端获得可移植性)与数据存储关注点(服务器获得可伸缩性)分离开来。 无状态:在客户端和服务器之间实现通信时,服务器在处理请求时永远不会利用储存在服务器上下文中的任何信息,而与会话相关的所有信息都存储在客户端中。 缓存:当可以(隐式或显式)缓存请求的响应时,客户端应获取缓存的响应。 统一接口:所有REST服务都应依赖组件之间相同的统一设计。接口应与提供的服务解耦。 分层系统:客户端永远不知道它们是直接连接到服务器还是连接到某些中间服务器。例如,请求可以通过代理,该代理具有负载平衡或共享缓存的功能。
图1:Richardson成熟度模型的级别
POX沼泽:只有一种资源和一种请求方法POST,并且只有一种通信方式XML。 资源:我们坚持使用POST方法,但是我们获得了更多可以处理的资源。 HTTP动词:目前在适当的情况下(资源),我们正在使用其他HTTP方法,例如GET或DELETE。通常,CRUD操作在此处实现。 超媒体控件:HATEOAS(应用程序状态的超文本引擎),应为客户端提供一个使用服务的启动链接,然后,每个响应都应包含指向该服务其他可能性的超链接。
可用性:客户端开发人员可以根据你提供的链接来有效地使用、了解和探索你的服务。而且,他们可以想象你项目的框架。 可伸缩性:遵循所提供的链接而不是不依赖于服务的代码更改来构造链接的客户端。 灵活性:提供服务较老版本和较新版本的链接,使你可以轻松地与基于旧版本的客户端和基于新版本的客户端进行互操作。 有效性:依赖HATEOAS的客户端永远不必担心服务器上的新版本或代码更改(如硬编码的版本)。 松耦合:HATEOAS通过分配构建和提供链接到服务器的职责来促进客户端和服务器之间的松耦合。
spring.data.mongodb.host= //Mongo server host
spring.data.mongodb.port= //Mongo server port
spring.data.mongodb.username= //Login user
spring.data.mongodb.password= //Password
spring.data.mongodb.database= //Database name
spring.data.mongodb.database = EmployeeManager
"C:\Program Files\MongoDB\Server\3.6\bin\mongod" --dbpath D:\Inther\EmployeeManager\warehouse-data\db
public class Employee {
private String employeeId;
private String firstName;
private String lastName;
private int age;
}
public class Department {
private String department;
private String name;
private String description;
private List<Employee> employees;
}
public interface EmployeeRepository
extends MongoRepository<Employee, String> {
}
public interface DepartmentRepository
extends MongoRepository<Department,String>{
}
“惰性”(查询创建):此策略将尝试通过分析查询方法的名称并推断关键字(例如findByLastnameAndFirstname)来构建查询。 编写查询:这里没有什么特别的。例如,只用@Query注释你的方法,然后自己编写查询。你也可以在MongoDB中编写查询。下面是基于JSON的查询方法的示例:
@Query("{ 'firstname' : ?0 }")
List<Employee> findByTheEmployeesFirstname(String firstname);
@Id :字段级别注释,指出你的哪个字段是身份标识。 @Document :类级别的注释,用于表示该类将被持久化到数据库中。 @DBRef :描述参考性的字段级别注释。
@Bean public CommandLineRunner init(EmployeeRepository employeeRepository, DepartmentRepository departmentRepository) {
return (args) -> {
employeeRepository.deleteAll();
departmentRepository.deleteAll();
Employee e = employeeRepository.save(new Employee("Ion", "Pascari", 23));
departmentRepository.save(new Department("Service Department", "Service Rocks!", Arrays.asList(e)));
for (Department d : departmentRepository.findAll()) {
LOGGER.info("Department: " + d);
}
};
}
public interface EmployeeService {
Employee saveEmployee(Employee e);
Employee findByEmployeeId(String employeeId);
void deleteByEmployeeId(String employeeId);
void updateEmployee(Employee e);
boolean employeeExists(Employee e);
List<Employee> findAll();
void deleteAll();
}
@Service public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public Employee saveEmployee(Employee e) {
return employeeRepository.save(e);
}
@Override
public Employee findByEmployeeId(String employeeId) {
return employeeRepository.findOne(employeeId);
}
@Override
public void deleteByEmployeeId(String employeeId) {
employeeRepository.delete(employeeId);
}
@Override
public void updateEmployee(Employee e) {
employeeRepository.save(e);
}
@Override
public boolean employeeExists(Employee e) {
return employeeRepository.exists(Example.of(e));
}
@Override
public List<Employee> findAll() {
return employeeRepository.findAll();
}
@Override
public void deleteAll() {
employeeRepository.deleteAll();
}
}
@RestController
@RequestMapping("/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@RequestMapping(value = "/list/", method = RequestMethod.GET)
public HttpEntity<List<Employee>> getAllEmployees() {
List<Employee> employees = employeeService.findAll();
if (employees.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(employees, HttpStatus.OK);
}
}
@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public HttpEntity<Employee> getEmployeeById(@PathVariable("id") String employeeId) {
Employee byEmployeeId = employeeService.findByEmployeeId(employeeId);
if (byEmployeeId == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else {
return new ResponseEntity<>(byEmployeeId, HttpStatus.OK);
}
}
@RequestMapping(value = "/employee/", method = RequestMethod.POST)
public HttpEntity<?> saveEmployee(@RequestBody Employee e) {
if (employeeService.employeeExists(e)) {
return new ResponseEntity<>(HttpStatus.CONFLICT);
} else {
Employee employee = employeeService.saveEmployee(e);
URI location = ServletUriComponentsBuilder .fromCurrentRequest().path("/employees/employee/{id}")
.buildAndExpand(employee.getEmployeeId()).toUri();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(location);
return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
}
}
@RequestMapping(value = "/employee/{id}", method = RequestMethod.PUT)
public HttpEntity<?> updateEmployee(@PathVariable("id") String id, @RequestBody Employee e) {
Employee byEmployeeId = employeeService.findByEmployeeId(id);
if(byEmployeeId == null){
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else {
byEmployeeId.setAge(e.getAge());
byEmployeeId.setFirstName(e.getFirstName());
byEmployeeId.setLastName(e.getLastName());
employeeService.updateEmployee(byEmployeeId);
return new ResponseEntity<>(employeeService, HttpStatus.OK);
}
}
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> deleteEmployee(@PathVariable("id") String employeeId) {
employeeService.deleteByEmployeeId(employeeId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@RequestMapping(value = "/employee/", method = RequestMethod.DELETE)
public ResponseEntity<?> deleteAll() {
employeeService.deleteAll();
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
@Document
public class Employee extends ResourceSupport{...}
@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public HttpEntity<Employee> getEmployeeById(@PathVariable("id") String employeeId) {
Employee byEmployeeId = employeeService.findByEmployeeId(employeeId);
if (byEmployeeId == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else { byEmployeeId.add(linkTo(methodOn(EmployeeController.class).getEmployeeById(byEmployeeId.getEmployeeId())).withSelfRel());
return new ResponseEntity<>(byEmployeeId, HttpStatus.OK);
}
}
add():设置链接值的方法。 linkTo(Class controller):一个静态导入的方法,该方法允许创建一个新的ControllerLinkBuilder,它的基类指向控制器类。 methodOn(Class controller,Object ... parameters):静态导入的方法,它创建到控制器类的间接引用,从而能够从该类调用方法并使用其返回类型。 withSelfRel():一种最终创建链接的方法,该链接默认具有指向自身的关系。
"employeeId": "5a6f67519fea6938e0196c4d",
"firstName": "Ion",
"lastName": "Pascari",
"age": 23,
"_links": {
"self": {
"href": "http://localhost:8080/employees/employee/5a6f67519fea6938e0196c4d"
}
}
}
_links代表资源表示的新设置值。 self代表链接指向的关系类型。在这种情况下,它是一个自引用超链接。也可能有其他类型的关系,例如指向另一个类(我们将在稍后介绍)。 href是标识资源的URL。
@RequestMapping(value = "/list/", method = RequestMethod.GET)
public HttpEntity<List<Department>> getAllDepartments() {
List<Department> departments = departmentService.findAll();
if (departments.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT)
} else {
departments.forEach(d -> d.add(linkTo(methodOn(DepartmentController.class).getAllDepartments()).withRel("departments")));
departments.forEach(d -> d.add(linkTo(methodOn(DepartmentController.class).getDepartmentById(d.getDepartmentId())).withSelfRel()));
departments.forEach(d -> d.getEmployees().forEach(e -> { e.add(linkTo(methodOn(EmployeeController.class).getAllEmployees()).withRel("employees"));
e.add(linkTo(methodOn(EmployeeController.class).getEmployeeById(e.getEmployeeId())).withSelfRel());
}));
return new ResponseEntity<>(departments, HttpStatus.OK);
}
}
{
"departmentId": "5a6f6c269fea690904a02657",
"name": "Service Department",
"description": "Service Rocks!",
"employees": [
{
"employeeId": "5a6f6c269fea690904a02656",
"firstName": "Ion",
"lastName": "Pascari",
"age": 23,
"_links": {
"employees": {
"href": "http://localhost:8080/employees/list/"
},
"self": {
"href": "http://localhost:8080/employees/employee/5a6f6c269fea690904a02656"
}
}
}
],
"_links": {
"departments": {
"href": "http://localhost:8080/departments/list/"
},
"self": {
"href": "http://localhost:8080/departments/department/5a6f6c269fea690904a02657"
}
}
}
withRel(String rel):一种方法,该方法最终以指向给定rel的关系创建Link。
另一种声音:容器是不是未来? GitHub 疑遭中间人攻击,最大暗网托管商再被黑! 漫画:什么是 “模因” ? 1 分钟抗住 10 亿请求!某些 App 怎么做到的?| 原力计划 2020,国产AI开源框架“亮剑”TensorFlow、PyTorch
探索比特币独特时间链、挖矿费用及场外交易的概念