提供网站建设教学视频,广州城市建设档案网站,德州网站优化公司,网站建设用什么教材作者 | Ion Pascari译者 | 天道酬勤 责编 | 徐威龙封图| CSDN 下载于视觉中国最近#xff0c;作者在把HATEOAS实现到REST Web服务时遇到了一件有趣的事情#xff0c;而且他也很幸运地尝试了一个名为MongoDB的NoSQL数据库#xff0c;他发现该数据库在许多不需要管理实务的不同… 作者 | Ion Pascari译者 | 天道酬勤 责编 | 徐威龙封图| CSDN 下载于视觉中国最近作者在把HATEOAS实现到REST Web服务时遇到了一件有趣的事情而且他也很幸运地尝试了一个名为MongoDB的NoSQL数据库他发现该数据库在许多不需要管理实务的不同情况下非常方便。因此今天他将和我们分享这种经验。也许我们中的一些人可能会学到一些新的东西即使已经已经学过但仍然可以对已经学知识有一个巩固复习。下面我们来看一下作者是如何使用MongoDB和HATEOAS创建REST Web服务该服务可以实现Richardson成熟度模型的第三级。首先我们来介绍一下REST然后逐步介绍HATEOAS和MongoDB。那么REST是什么呢 REST 万维网联盟指出REST是一个如何构建Web服务的模型。REST Web是WWW基于HTTP的子集其中代理提供统一的接口语义本质上是创建检索更新和删除而不是任意或特定于应用程序的接口并且仅通过交换表示来操纵资源。那么现在我们知道REST是什么了作者将简要列出Roy Fielding在其论文的第五章中提到的所有约束客户端-服务器以这样的方式实施服务将用户界面关注点客户端获得可移植性与数据存储关注点服务器获得可伸缩性分离开来。无状态在客户端和服务器之间实现通信时服务器在处理请求时永远不会利用储存在服务器上下文中的任何信息而与会话相关的所有信息都存储在客户端中。缓存当可以隐式或显式缓存请求的响应时客户端应获取缓存的响应。统一接口所有REST服务都应依赖组件之间相同的统一设计。接口应与提供的服务解耦。分层系统客户端永远不知道它们是直接连接到服务器还是连接到某些中间服务器。例如请求可以通过代理该代理具有负载平衡或共享缓存的功能。Richardson成熟度模型 图1Richardson成熟度模型的级别正如Martin Fowler所说该模型是“由Leonard Richardson开发的模型它将REST方法的主要元素分解为三个步骤。这些步骤引入了资源、HTTP动词和超媒体控件”。 这里简要介绍一下这些级别POX沼泽只有一种资源和一种请求方法POST并且只有一种通信方式XML。资源我们坚持使用POST方法但是我们获得了更多可以处理的资源。HTTP动词目前在适当的情况下资源我们正在使用其他HTTP方法例如GET或DELETE。通常CRUD操作在此处实现。超媒体控件HATEOAS应用程序状态的超文本引擎应为客户端提供一个使用服务的启动链接然后每个响应都应包含指向该服务其他可能性的超链接。既然我们知道了REST并且已经介绍了它的成熟度模型接下来我们再简要介绍一个NoSQL数据库MongoDB然后我们将进行演示 为什么选择HATEOAS 首先我们要指出REST并不容易没有真正理解REST的人会说这很容易。通常对于短期内不会增长或更改的小型服务如果你达到了第二级HTTP动词那就更好了。那么那些正在增长的大型服务呢很多人会说只要你做第二级就可以了。为什么因为HATEOAS是使REST变得复杂的原因之一这是困难的。如果你真的想获得其优势则必须在客户端上编写更多代码——处理错误、如何解释资源、如何分析提供的链接和服务器来构建全面而有用的链接等。让我们来看看HATEOAS其中的一些优势可用性客户端开发人员可以根据你提供的链接来有效地使用、了解和探索你的服务。而且他们可以想象你项目的框架。可伸缩性遵循所提供的链接而不是不依赖于服务的代码更改来构造链接的客户端。灵活性提供服务较老版本和较新版本的链接使你可以轻松地与基于旧版本的客户端和基于新版本的客户端进行互操作。有效性依赖HATEOAS的客户端永远不必担心服务器上的新版本或代码更改如硬编码的版本。松耦合HATEOAS通过分配构建和提供链接到服务器的职责来促进客户端和服务器之间的松耦合。NoSQLMongoDB 那么什么是NoSQL数据库从名称“非SQL”或“非关系型”衍生而来这些数据库不使用类似SQL的查询语言通常称为结构化存储。这些数据库自1960年就已经存在但是直到现在一些大公司例如Google和Facebook开始使用它们时这些数据库才流行起来。该数据库最明显的优势是摆脱了一组固定的列、连接和类似SQL的查询语言的限制。有时NoSQL这个名称也可能表示“不仅仅SQL”来确保它们可能支持SQL。 NoSQL数据库使用诸如键值、宽列、图形或文档之类的数据结构并且可以如JSON之类的不同格式存储。 MongoDB是一种无模式的NoSQL数据库它是面向文档的因此如上所述它提供了高性能和良好的可伸缩性并且是跨平台的。 之所以推荐MongoDB是因为它具有完整的索引支持JSON格式的对象存储结构简单明了出色的动态文档查询支持不必将应用程序对象转换到到数据库对象以及MongoDB的专业支持。 准备使用MongoDB来编写代码 现在我们准备进行正题。让我们构建一个简单的EmployeeManager Web服务我们将使用它来演示与MongoDB连接的HATEOAS。为了引导我们的应用程序我们将使用Spring Initializr。我们将使用Spring HATEOAS和Spring Data MongoDB作为依赖项。你应该看到类似下图2所示的内容。 图2 引导应用程序配置完成后下载zip并将其作为Maven项目导入你喜欢的IDE中。 首先让我们配置application.properties。要获得MongoDB连接你应该处理以下参数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
一般来说如果所有内容都是全新安装的并且你没有更改或修改任何Mongo属性则只需提供一个数据库名称已经通过GUI创建了一个数据库名称。spring.data.mongodb.database EmployeeManager另外为了启动Mongo实例作者创建了一个.bat它指向安装文件夹和数据文件夹。它是这样的C:\Program Files\MongoDB\Server\3.6\bin\mongod --dbpath D:\Inther\EmployeeManager\warehouse-data\db
现在我们来快速创建模型。这里有两个模型员工模型和部门模型。检查它们确保有没有参数、getter、setter、equals方法和hashCode生成的构造函数。不用担心所有代码都在GitHub上你可以稍后查看它https://github.com/theFaustus/EmployeeManager。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 ListEmployee employees;
}
现在我们已经完成了模型的制作让我们来创建存储库以便来测试持久性。存储库如下所示public interface EmployeeRepository
extends MongoRepositoryEmployee, String {
}
public interface DepartmentRepository
extends MongoRepositoryDepartment,String{
}
如上所示这里没有方法因为大家都知道Spring Data中的中心接口被命名为Repository在其之上是CrudRepository它提供了处理模型的基本操作。在CrudRepository之上我们有PagingAndSortingRepository它为我们提供了一些扩展功能来简化分页和排序访问。在我们的案例中最重要的是MongoRepository它用于严格处理我们的Mongo实例。因此对于我们的案例来说除了那些现成的方法外我们不需要任何其他方法但是仅出于学习目的作者在这里要提到的是你可以添加其他查询方法的两种方法“惰性”查询创建此策略将尝试通过分析查询方法的名称并推断关键字例如findByLastnameAndFirstname来构建查询。编写查询这里没有什么特别的。例如只用Query注释你的方法然后自己编写查询。你也可以在MongoDB中编写查询。下面是基于JSON的查询方法的示例Query({ firstname : ?0 })
ListEmployee findByTheEmployeesFirstname(String firstname);
至此我们已经可以测试我们持久性如何工作。我们只需要对模型进行一些调整即可。通过调整作者的意思是我们需要注释一些东西。Spring Data MongoDB使用MappingMongoConverter将对象映射到文档下面是我们将要使用的一些注释Id 字段级别注释指出你的哪个字段是身份标识。Document 类级别的注释用于表示该类将被持久化到数据库中。DBRef 描述参考性的字段级别注释。注释完成后我们可以使用CommandLineRunner获取数据库中的一些数据CommandLineRunner是一个接口用于在应用程序完全启动时即在run方法之前运行代码段。在下面你可以看一下作者的Bean配置。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);}};
}
我们已经创建了一些模型并对它们进行了持久化。现在我们需要一种与他们交互的方式。如上所说所有代码都可以在GitHub上找到因此作者在这里将仅向我们展示一个域服务接口和实现。接口如下public interface EmployeeService {Employee saveEmployee(Employee e);Employee findByEmployeeId(String employeeId);void deleteByEmployeeId(String employeeId);void updateEmployee(Employee e);boolean employeeExists(Employee e);ListEmployee findAll();void deleteAll();
}
接口的实现如下Service public class EmployeeServiceImpl implements EmployeeService {Autowiredprivate EmployeeRepository employeeRepository;Overridepublic Employee saveEmployee(Employee e) {return employeeRepository.save(e);}Overridepublic Employee findByEmployeeId(String employeeId) {return employeeRepository.findOne(employeeId);}Overridepublic void deleteByEmployeeId(String employeeId) {employeeRepository.delete(employeeId);}Overridepublic void updateEmployee(Employee e) {employeeRepository.save(e);}Overridepublic boolean employeeExists(Employee e) {return employeeRepository.exists(Example.of(e));}Overridepublic ListEmployee findAll() {return employeeRepository.findAll();}Overridepublic void deleteAll() {employeeRepository.deleteAll();}
}
这里没有什么特别的要注意的下面我们将继续讨论最后一个难题——控制器你可以在下面看到员工资源的控制器实现。RestController
RequestMapping(/employees)
public class EmployeeController {Autowiredprivate EmployeeService employeeService;RequestMapping(value /list/, method RequestMethod.GET)public HttpEntityListEmployee getAllEmployees() {ListEmployee 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 HttpEntityEmployee 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);}
}
因此对于上面实现的所有方法我们将自己定位在Richardson成熟度模型的第二级因为我们使用了HTTP动词并实现了CRUD操作。现在我们有了与数据进行交互的方法并且可以使用Postman我们可以如下图3所示检索资源或者可以如下图4所示添加新资源。 图3 检索JSON中的部门列表 图4JSON中添加新员工HATEOAS即将来临 绝大多数人都止步于此因为通常情况下对他们或Web服务而言这已经就足够了但这不是我们在这里的原因。因此如前所述支持HATEOAS或超媒体驱动的站点的Web服务应该能够提供有关如何使用和导航Web服务的信息方法是包含与响应之间具有某种关系的链接。 你可以将HATEOAS想象成一个路标。当你开车的时候这些标志会指引你。例如如果你需要到达机场则只需遵循指示标志如果你需要返回则再次遵循指示标志就可以了而且你一直知道你可以待在哪里、停车或开车等等。 让我们实现资源表示形式附带的链接我们必须通过扩展ResourceSupport来继承add方法来调整模型这给我们提供一个不错的选择可以为资源表示形式设置值而无需添加任何新字段 。Document
public class Employee extends ResourceSupport{...}
现在让我们开始创建链接。为此Spring HATEOAS提供了一个Link对象来存储这种信息并提供CommandLinkBuilder来构建它。 假设我们想要为员工id添加一个GET响应的链接。RequestMapping(value /employee/{id}, method RequestMethod.GET)
public HttpEntityEmployee 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设置链接值的方法。linkToClass controller一个静态导入的方法该方法允许创建一个新的ControllerLinkBuilder它的基类指向控制器类。methodOnClass controllerObject ... parameters静态导入的方法它创建到控制器类的间接引用从而能够从该类调用方法并使用其返回类型。withSelfRel一种最终创建链接的方法该链接默认具有指向自身的关系。现在GET将产生以下响应{employeeId: 5a6f67519fea6938e0196c4d,firstName: Ion,lastName: Pascari,age: 23,_links: {self: {href: http://localhost:8080/employees/employee/5a6f67519fea6938e0196c4d}}
}响应不仅包含员工的详细信息还包含可在其中导航的自链接URL。_links代表资源表示的新设置值。self代表链接指向的关系类型。在这种情况下它是一个自引用超链接。也可能有其他类型的关系例如指向另一个类我们将在稍后介绍。href是标识资源的URL。现在假设我们要为部门列表添加指向GET响应的链接。在这里事情变得越来越有趣因为部门不仅指向自己也指向员工员工也指向自己和他们的列表。因此让我们看一下代码RequestMapping(value /list/, method RequestMethod.GET)
public HttpEntityListDepartment getAllDepartments() {ListDepartment 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}}
}除了存在一些未命名为self的关系链接之外没有任何改变。这些是作者之前谈到的其他类型的关系它们是与withRelString rel建立在一起的
withRelString rel一种方法该方法最终以指向给定rel的关系创建Link。恭喜你 到这里我们可以说已经达到了Richardson成熟度模型的第3级当然我们之所以没有这样做是因为我们需要对Web服务进行更多的检查和改进例如提供有关资源状态或任何其他事物的链接但是我们几乎做到了你可以在此处获得完整的GitHub源代码https://github.com/theFaustus/EmployeeManager希望你能喜欢如果有不清楚的地方或其他意见欢迎评论区留言告诉我们或者和我们讨论。推荐阅读另一种声音容器是不是未来
GitHub 疑遭中间人攻击最大暗网托管商再被黑
漫画什么是 “模因”
1 分钟抗住 10 亿请求某些 App 怎么做到的| 原力计划
2020国产AI开源框架“亮剑”TensorFlow、PyTorch
探索比特币独特时间链、挖矿费用及场外交易的概念
真香朕在看了