使用Spring Boot和MongoDB构建一个反应式应用程序


如果您处理大量流数据,反应性应用程序可以让您更好地扩展。它们是非阻塞的,并且往往更有效率,因为它们在等待事情发生时不会占用处理。
反应系统包含异步I / O. 异步I / O背后的概念很简单:通过回收资源来缓解低效的资源利用率,否则这些资源在等待I / O活动时会处于空闲状态。异步I / O反转了I / O处理的正常设计:客户端会收到新数据的通知而不是主动请求拉取数据; 这样可以让客户端在等待数据通知来之前做其他事情。
如果您要构建一个响应式应用程序,您将需要它一直Reactive到您的数据库。在Spring WebFlux中使用阻塞JDBC驱动程序,你会对其性能感到失望。使用像Cassandra,MongoDB,Couchbase和Redis这样的反应性NoSQL数据库 - 你则会对它的性能印象深刻。
在本教程中,您将学习如何使用Spring Boot,Spring WebFlux和Spring Data创建一个与NoSQL数据库后端(在本例中为MongoDB)对话的响应式Web服务。


什么是NoSQL以及为什么选择MongoDB?
NoSQL是任何非关系数据库的术语。在关系数据库(想想SQL,MySQL等等)中,数据存储在表格列中具有强类型和明确定义的关系的表中。关系数据库的紧密,明确的结构既是他们的优势,也是他们的弱点。这是一种权衡。NoSQL数据库打破了这个模型,并提供了其他模型,允许更大的灵活性和易于扩展。
缩放的微服务/集群模型为关系数据库带来了许多问题。它们不是为了在多台机器上运行并保持数据同步而构建的。而部分地使用NoSQL数据库可以来解决这个问题,通常,NoSQL在构建时考虑了聚合类和水平缩放。
NoSQL数据库的另一个潜在好处是它们的灵活性。像MongoDB这样的基于文档的NoSQL数据库可以在文档中存储任意数据。可以动态地将字段添加到存储的文档中,而不需要表迁移的开销。当然,这并没有解决版本控制的问题,它仍然取决于应用程序来处理不断变化的数据结构(并不总是微不足道),但至少你不是在与数据库作斗争(基于数据库编程)。

SQL /关系数据库已经经过验证,快速且超级可靠。在某些用例中,它们更便宜,更容易。例如,对于简单的网站或博客来说,MySQL就很胜任。但即使在企业环境中,有时您也需要关系数据库强制执行的结构。如果您有一个相当静态的数据模型,并且不需要扩展到Internet互联网规模,那么SQL可能是最佳选择。
我在本教程中使用的是MongoDB,因为从一开始就轻而易举。如果你使用Spring Data MongoDB,那就更容易了!

Reactive反应式编程
Reactive是另一个很大的行话。这种感觉就像人们喜欢在派对和会议上挥之不去,只是模糊地了解它实际意味着什么。就像“存在主义”或“恩赐”一样,让我们来定义它。
如果您查看Spring WebFlux文档,它们可以非常好地概述反应意味着什么。

术语“反应性”是指围绕变化做出反应的编程模型 - 对I / O事件做出反应的网络组件,对鼠标事件做出反应的UI控制器等。从这个意义上说,非阻塞是Reactive响应式的,因为我们现在处于一种模式,即在操作完成或数据可用时对通知作出反应。

因此,反应/响应意味着:非阻塞,异步,并以流处理为中心。

构建Spring Boot资源服务器
从GitHub存储库克隆启动项目并检查启动分支:

git clone -b start https://github.com/oktadeveloper/okta-spring-boot-mongo-webflux-example.git

入门项目是一个简单的Spring Boot入门项目,其中已包含必要的依赖项build.gradle。
让我们快速浏览一下依赖项:

compile('org.springframework.boot:spring-boot-starter-webflux')  
compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')  
compileOnly('org.projectlombok:lombok')  
compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo')

第一个是Spring WebFlux,Spring MVC的反应版本。第二个引入Spring需要的响应式MongoDB依赖项。第三个是名为Lombok的项目,它使我们在Java代码中无需键入一堆构造函数,getter和setter(您可以在他们的网页上查看项目)。最后一个依赖项是嵌入式内存中的MongoDB数据库。这个数据库非常适合测试,这样的简单教程,并且不会真正持久化。
可以使用简单的Gradle命令运行应用程序:

./gradlew bootRun

为MongoDB定义模型类

模型类:


@Document
@Data  
@AllArgsConstructor  
@NoArgsConstructor  
public class Kayak {  
    private String name;  
    private String owner;  
    private Number value;  
    private String makeModel;  
}

@Document注释是NoSQL的类似关系数据库的@Entity。它告诉Spring Boot定义了一个数据模型。在NoSQL世界中,这意味着创建文档而不是表条目。其他三个注释是自动生成getter,setter和构造函数的Lombok助手。
Kayak文档有五个属性:名称name,所有者owner,值value和类型makeModel。它们会自动映射到MongoDB的相应BSON类型。
什么是BSON类型?看一下关于这个主题的MongoDB文档。它们是用于在MongoDB文档中保留数据的二进制序列化类型。它们定义了可以存储在MongoDB数据库中的基元类型。

将ReactiveMongoRepository添加到Spring Boot App
使用@Document注释定义Kayak类告诉Spring Boot有关数据结构的信息,但实际上并没有给我们任何保存或加载数据库数据的方法。为此,您需要定义一个存储库。
这个代码非常简单:KayakRepository.java:

  
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;  

public interface KayakRepository extends ReactiveMongoRepository<Kayak, Long> {  
}

这实际上为您提供了从数据库创建,更新,读取和删除文档所需的所有基本方法。要了解如何,特别是深入研究ReactiveMongoRepository班级和各种其他超类ReactiveCrudRepository。看看该文档的ReactiveCrudRepository看到实现的方法。
ReactiveCrudRepository实际上提供了一套基本而完整的CRUD方法。ReactiveMongoRepository在此基础上构建,以提供一些特定于MongoDB的查询功能。

用Spring WebFlux实现一个Controller
添加存储库后,您就可以以编程方式操作数据。但是,我们还没有定义Web端点。必须明确定义公共Web端点KayakController.java:


@Controller  
@RequestMapping(path = "/kayaks")  
public class KayakController {  
  
    private KayakRepository kayakRepository;  
    
    public KayakController(KayakRepository kayakRepository) {
        this.kayakRepository = kayakRepository;
    }
  
    @PostMapping()  
    public @ResponseBody  
    Mono<Kayak> addKayak(@RequestBody Kayak kayak) {  
        return kayakRepository.save(kayak);  
    }  
  
    @GetMapping()  
    public @ResponseBody  
    Flux<Kayak> getAllKayaks() {  
        return kayakRepository.findAll();  
    }
}

该控制器添加两个端点:

  • POST /kayaks增加了一条新的Kayak皮划艇
  • GET /kayaks列出了所有的Kayak皮划艇

该类使用Spring依赖注入将KayakRepository实例自动装入控制器,您将看到如何使用存储库持久化Kayak域类。
(与传统控制器区别是Spring WebFlux返回Flux类型和Mono类型)

测试Spring Boot Server
此时,您拥有一个完全可操作的皮划艇REST资源服务器。在测试之前,请将以下方法添加到您的MainApplication课程中。这只是在应用程序加载时将一些测试数据注入数据库。

@Bean  
ApplicationRunner init(KayakRepository repository) {  
  
  Object[][] data = {  
      {"sea", "Andrew", 300.12, "NDK"},  
      {
"creek", "Andrew", 100.75, "Piranha"},  
      {
"loaner", "Andrew", 75, "Necky"}  
  };  
  
  return args -> {  
      repository  
          .deleteAll()  
          .thenMany(  
              Flux  
                  .just(data)  
                  .map(array -> {  
                      return new Kayak((String) array[0], (String) array[1], (Number) array[2], (String) array[3]);  
                  })  
                  .flatMap(repository::save)  
          )  
          .thenMany(repository.findAll())  
          .subscribe(kayak -> System.out.println(
"saving " + kayak.toString()));
  };  
}

HTTPie是一个很棒的命令行实用程序,可以轻松地对资源服务器运行请求。如果您没有安装HTTPie,请使用安装它brew install httpie。或者前往他们的网站并实现目标。

确保您的Spring Boot应用程序正在运行。如果不是,请使用它
./gradlew bootRun。
对资源服务器运行GET请求:http :8080/kayaks这是简写
http GET http://localhost:8080/kayaks。
你会得到这个:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
transfer-encoding: chunked
[
    {
        "makeModel": "NDK",
       
"name": "sea",
       
"owner": "Andrew",
       
"value": 300.12
    },
    {
       
"makeModel": "Piranha",
       
"name": "creek",
       
"owner": "Andrew",
       
"value": 100.75
    },
    {
       
"makeModel": "Necky",
       
"name": "loaner",
       
"owner": "Andrew",
       
"value": 75
    }
]

现在尝试POST一条新的皮划艇到服务器。

http POST :8080/kayaks name="sea2" owner="Andrew" value="500" makeModel="P&H"

你应该看到:

HTTP/1.1 200 OK
Content-Length: 62
Content-Type: application/json;charset=UTF-8
{
    "makeModel": "P&H",
   
"name": "sea2",
   
"owner": "Andrew",
   
"value": 500
}