Building IoT Applications Using Fauna and Spring – 使用Fauna和Spring构建物联网应用

最后修改: 2022年 8月 22日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this article, we’ll build an IoT application powered by Fauna and Spring.

在本文中,我们将构建一个由Fauna和Spring驱动的IoT应用程序。

2. IoT Applications – Fast Edges and Distributed Databases

2.物联网应用–快速边缘和分布式数据库

IoT applications work close to the users. They are responsible for consuming and processing large volumes of real-time data with low latency. They need fast Edge Computing servers and distributed databases to achieve low latency and maximum performance.

物联网应用在用户身边工作。它们负责以低延迟消费和处理大量的实时数据。它们需要快速的边缘计算服务器和分布式数据库来实现低延迟和最大性能。

Also, IoT applications process unstructured data, mainly because of the varied source from which they consume them. IoT applications need databases that can process these unstructured data efficiently.

另外,物联网应用程序处理非结构化数据,主要是因为它们消费这些数据的来源多种多样。物联网应用需要能够有效处理这些非结构化数据的数据库。

In this article, we’ll build the backend of an IoT application responsible for processing and storing the health vitals of individuals, such as temperature, heart rate, blood oxygen level, etc. This IoT application can consume health vitals from cameras, infra-red scanners, or sensors via wearables such as smartwatches.

在这篇文章中,我们将建立一个物联网应用的后台,负责处理和存储个人的健康体征,如体温、心率、血氧水平等。这个物联网应用可以通过摄像头、红外线扫描仪或智能手表等可穿戴设备的传感器来获取健康体征。

3. Using Fauna for IoT Applications

3.在物联网应用中使用Fauna

In the previous section, we learned the features of a typical IoT application, and we also understand the expectation one has from a database to be used for the IoT space.

在上一节中,我们了解了一个典型的物联网应用的特点,我们也了解了人们对用于物联网领域的数据库的期望。

Fauna, due to its below features, can be best suited to be used as a database in an IoT application:

Fauna由于其以下特点,可以最适合在物联网应用中作为数据库使用。

Distributed: When we create an application in Fauna, it automatically distributes across multiple cloud regions. It is hugely beneficial for Edge Computing applications using technologies like Cloudflare Workers or Fastly Compute @ Edge. For our use case, this feature can help us quickly access, process, and store the health vitals of individuals distributed across the globe with minimum latency.

分布式: 当我们在 Fauna 中创建应用程序时,它会自动分布在多个云区域。这对使用 Cloudflare Workers 或 Fastly Compute @ Edge 等技术的边缘计算应用程序大有裨益。对于我们的用例,该功能可以帮助我们以最小的延迟快速访问、处理和存储分布在全球各地的个人的健康体征。

Document-relational: Fauna combines the flexibility and familiarity of JSON documents with the relationships and querying power of a traditional relational database. This capability helps in processing unstructured IoT data at scale.

文档-关系型: Fauna将JSON文档的灵活性和熟悉性与传统关系型数据库的关系和查询能力相结合。这种能力有助于大规模地处理非结构化的物联网数据。

Serverless: With Fauna – we can purely focus on building our application, not the administrative and infrastructure overhead associated with databases.

无服务器:使用Fauna – 我们可以纯粹地专注于构建我们的应用程序,而不是与数据库相关的管理和基础设施开销

4. High-Level Request Flow of the Application

4.应用程序的高级请求流

On a high level, this is what the request flow of our application will look like:

在高层次上,这就是我们的应用程序的请求流将是什么样子。

request flow

Here Aggregator is a simple application that aggregates the data received from various sensors. In this article, we will not focus on building the Aggregator, but we can solve its purpose by a simple Lambda function deployed on the cloud.

这里的Aggregator是一个简单的应用程序,它可以聚合从各种传感器收到的数据。在这篇文章中,我们不会专注于构建Aggregator,但我们可以通过部署在云端的简单Lambda函数来解决其目的。

Next, we will build the Edge Service using Spring Boot and set up the Fauna database with different region groups to handle requests from different regions.

接下来,我们将使用Spring Boot构建Edge服务,并将Fauna数据库设置为不同的区域组以处理来自不同区域的请求。

5. Creating Edge Service – Using Spring Boot

5.创建边缘服务–使用Spring Boot

Let’s create the Edge Service using Spring Boot that will consume the data from the health sensors and push it to the appropriate Fauna region.

让我们使用Spring Boot创建Edge服务,它将消费来自健康传感器的数据,并将其推送到适当的Fauna区域。

In our previous tutorials on Fauna, Introduction to FaunaDB with Spring and Building a web app Using Fauna and Spring for Your First web Agency Client, we explored how to create a Spring Boot application that connects with Fauna. Feel free to dive into these articles for more details.

在我们之前关于Fauna的教程中,用Spring介绍FaunaDB使用Fauna和Spring为您的第一个Web Agency客户端构建一个Web应用程序,我们探讨了如何创建一个与Fauna连接的Spring Boot应用。请随时深入阅读这些文章,了解更多细节。

5.1. Domain

5.1.领域

Let’s first understand the domain for our Edge Service.

首先让我们了解一下我们的边缘服务的领域。

As mentioned previously, our Edge Service will consume and process health vitals, let’s create a record having basic health vitals of an individual:

如前所述,我们的Edge服务将消费和处理健康体征,让我们创建一个具有个人基本健康体征的记录。

public record HealthData(
  
    String userId,
  
    float temperature, 
    float pulseRate,
    int bpSystolic,
    int bpDiastolic,
  
    double latitude, 
    double longitude, 
    ZonedDateTime timestamp) {
}

5.2. Health Service

5.2.卫生服务

Out health service will be responsible for processing the health data, identifying the region from the request, and routing it to the appropriate Fauna region:

外部健康服务将负责处理健康数据,从请求中确定区域,并将其转到适当的Fauna区域:

public interface HealthService {
    void process(HealthData healthData);
}

Let’s start building the implementation:

让我们开始建立实施。

public class DefaultHealthService implements HealthService {

    @Override
    public void process(HealthData healthData) {
        // ...
    }
}

Next, let’s add the code for identifying the request region, i.e. from where the request is triggered.

接下来,让我们添加识别请求区域的代码,即从哪里触发请求。

There are several libraries in Java to identify geolocation. But, for this article, we will add a simple implementation that returns the region “US” for all the requests.

在Java中有几个库可以识别地理位置。但是,在这篇文章中,我们将添加一个简单的实现,为所有的请求返回区域 “美国”。

Let’s add the interface:

我们来添加接口。

public interface GeoLocationService {
    String getRegion(double latitude, double longitude);
}

And the implementation that returns the “US” region for all requests:

以及为所有请求返回 “美国 “地区的实现。

public class DefaultGeoLocationService implements GeoLocationService {

    @Override
    public String getRegion(double latitude, double longitude) {
        return "US";
    }
}

Next, let’s use this GeoLocationService in our HealthService; let’s inject it:

接下来,让我们在我们的HealthService中使用这个GeoLocationService让我们注入它。

@Autowired
private GeoLocationService geoLocationService;

And use it in the process method to extract the region:

并在process方法中使用它来提取该区域。

public void process(HealthData healthData) {

    String region = geoLocationService.getRegion(
        healthData.latitude(), 
        healthData.longitude());
    
    // ...
}

Once we have the region, we have to query the appropriate Fauna region group to store the data, but before we get to it, let’s set up the Fauna databases with Region Groups. We will resume our integration after that.

一旦我们有了区域,我们必须查询适当的Fauna区域组来存储数据,但在这之前,让我们用区域组设置Fauna数据库。之后我们将继续我们的整合。

6. Fauna with Region Groups – Setup

6.带有区域组的动物群 – 设置

Let’s start with creating new databases in the Fauna. We need to create an account if we don’t already have one.

让我们先在Fauna中创建新的数据库。我们需要创建一个账户,如果我们还没有一个账户的话。

6.1. Creating Databases

6.1.创建数据库

Once we’ve logged in, let’s create a new database:

一旦我们登录了,让我们创建一个新的数据库。

fauna create db

Here, we’re creating this database in the Europe (EU) region; let’s create the same database in the US region to handle our requests from the United States:

这里,我们在欧洲(EU)地区创建这个数据库;让我们在美国地区创建同样的数据库,以处理来自美国的请求。

fauna db us

Next, we need a security key to access our database from outside, in our case, from the edge service we have created. We’ll create the keys separately for both databases:

接下来,我们需要一个安全密钥,以便从外部访问我们的数据库,在我们的例子中,从我们创建的边缘服务中访问。我们将为两个数据库分别创建密钥。

fauna key

Similarly, we’ll create the key for accessing the healthapp-us database. For more detailed instructions on creating databases and security keys in Fauna, head to our Introduction to FaunaDB with Spring article.

同样地,我们将创建用于访问healthapp-us数据库的密钥。有关在Fauna中创建数据库和安全密钥的更多详细说明,请访问我们的Introduction to FaunaDB with Spring文章。

Once keys are created, let’s store the region-specific Fauna connection URLs and security keys in the application.properties of our Spring Boot service:

一旦创建了密钥,让我们在Spring Boot服务的application.properties中存储特定区域的Fauna连接URL和安全密钥。

fauna-connections.EU=https://db.eu.fauna.com/
fauna-secrets.EU=eu-secret
fauna-connections.US=https://db.us.fauna.com/
fauna-secrets.US=us-secret

We’ll need these properties when we use them to configure the Fauna client to connect with  Fauna databases.

当我们使用这些属性来配置Fauna客户端与Fauna数据库连接时,我们将需要这些属性。

6.2. Creating a HealthData Collection

6.2.创建一个健康数据集合

Next, let’s create the HealthData collection in Fauna to store the health vitals of an individual.

接下来,让我们在Fauna中创建HealthData集合,以存储一个人的健康体征。

Let’s add the collection by navigating to the Collections tab in our database dashboard and clicking on the “New Collection” button:

让我们通过导航到数据库仪表板中的 “收藏 “选项卡并点击 “新收藏 “按钮来添加该收藏。

fauna collection

Next, let’s click on the “New Document” button on the next screen to insert one sample document and add the below JSON in the JavaScript console:

接下来,让我们点击下一个屏幕上的 “新文档 “按钮,插入一个样本文档,并在JavaScript控制台中添加以下JSON。

{
  "userId": "baeldung-user",
  "temperature": "37.2",
  "pulseRate": "90",
  "bpSystolic": "120",
  "bpDiastolic": "80",
  "latitude": "40.758896",
  "longitude": "-73.985130",
  "timestamp": Now()
}

The Now() function will insert the current timestamp in the timestamp field.

Now() 函数将在timestamp字段中插入当前时间戳。

As we hit save, the above data is inserted, and we can view all inserted documents in the Collection tab within our healthdata collection:

当我们点击保存时,上述数据被插入,我们可以在healthdata集合中的集合标签中查看所有插入的文件。

fauna collection view

Now that we have the databases, secret keys, and collections created, let’s go forward and integrate our Edge Service with our Fauna databases and perform operations on them.

现在我们已经创建了数据库、秘钥和集合,让我们继续前进,将我们的Edge服务与Fauna数据库整合,并对它们进行操作。

7. Integrating Edge Service with Fauna

7.将边缘服务与Fauna结合起来

To integrate our Spring Boot application with Fauna, we need to add the Fauna driver for Java to our project. Let’s add the dependency:

为了将我们的Spring Boot应用程序与Fauna集成,我们需要在我们的项目中添加Fauna的Java驱动。让我们来添加这个依赖关系。

<dependency>
    <groupId>com.faunadb</groupId>
    <artifactId>faunadb-java</artifactId>
    <version>4.2.0</version>
    <scope>compile</scope>
</dependency>

We can always find the latest version for faunadb-java here.

我们可以随时找到faunadb-java的最新版本这里

7.1. Creating Region-Specific Fauna Clients

7.1.创建特定区域的动物群客户端

This driver provides us FaunaClient that we can configure easily with a given connection endpoint and secret:

这个驱动程序为我们提供了FaunaClient,我们可以用给定的连接端点和秘密轻松配置。

FaunaClient client = FaunaClient.builder()
    .withEndpoint("connection-url")
    .withSecret("secret")
    .build();

In our application, depending on where the request comes from, we need to connect with Fauna’s EU and US regions. We can solve this by either pre-configuring different FaunaClient instances for both regions separately or dynamically configuring the client on runtime. Let’s work on the second approach.

在我们的应用程序中,根据请求的来源,我们需要与Fauna的欧盟和美国地区连接。我们可以通过为这两个地区分别预配置不同的FaunaClient实例或在运行时动态配置客户端来解决这个问题。让我们研究一下第二种方法。

Let’s create a new class, FaunaClients, that accepts a region and returns the correctly configured FaunaClient:

让我们创建一个新的类,FaunaClients,它接受一个区域并返回正确配置的FaunaClient

public class FaunaClients {

    public FaunaClient getFaunaClient(String region) {
        // ...
    }
}

We have already stored the Fauna’s region-specific endpoints and secrets in the application.properties; we can inject the endpoints and secrets as a map:

我们已经在application.properties中存储了Fauna的特定区域的端点和秘密;我们可以将端点和秘密作为一个地图注入。

@ConfigurationProperties
public class FaunaClients {

    private final Map<String, String> faunaConnections = new HashMap<>();
    private final Map<String, String> faunaSecrets = new HashMap<>();

    public Map<String, String> getFaunaConnections() {
        return faunaConnections;
    }

    public Map<String, String> getFaunaSecrets() {
        return faunaSecrets;
    }
}

Here, we have used @ConfigurationProperties, which injects the configuration properties in our class. To enable this annotation, we will also need to add:

在这里,我们使用了@ConfigurationProperties,它将配置属性注入我们的类中。为了启用这个注解,我们还需要添加。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

Finally, we need to pull the correct connection endpoint and secret from the respective maps and use them accordingly to create FaunaClient:

最后,我们需要从各自的地图中提取正确的连接端点和秘密,并相应地使用它们来创建FaunaClient

public FaunaClient getFaunaClient(String region) {

    String faunaUrl = faunaConnections.get(region);
    String faunaSecret = faunaSecrets.get(region);

    log.info("Creating Fauna Client for Region: {} with URL: {}", region, faunaUrl);
    
    return FaunaClient.builder()
        .withEndpoint(faunaUrl)
        .withSecret(faunaSecret)
        .build();
}

We have also added a log to check that the correct Fauna URL was picked while creating the client.

我们还加入了一个日志,以检查在创建客户端时是否选择了正确的Fauna URL。

7.2. Using Region-Specific Fauna Clients in Health Service

7.2.在卫生服务中使用特定区域的动物群落客户

Once our clients are ready, let’s use them in our Health Service to send health data to Fauna.

一旦我们的客户准备好了,让我们在我们的健康服务中使用它们,向Fauna发送健康数据。

Let’s inject the FaunaClients:

让我们注入FaunaClients

public class DefaultHealthService implements HealthService {
    
    @Autowired
    private FaunaClients faunaClients;
    
    // ...
}

Next, let’s get the region-specific FaunaClient by passing in the region extracted previously from the GeoLocationService:

接下来,让我们通过传递之前从GeoLocationService中提取的区域来获得特定区域的FaunaClient

public void process(HealthData healthData) {

    String region = geoLocationService.getRegion(
        healthData.latitude(), 
        healthData.longitude());
    
    FaunaClient faunaClient = faunaClients.getFaunaClient(region);
}

Once we have the region-specific FaunaClient, let’s use it to insert the Health data into the specific database.

一旦我们有了特定区域的FaunaClient,让我们用它来把健康数据插入特定的数据库中。

We’ll base this on our existing Faunadb Spring Web App article, where we have written several CRUD queries in FQL (Fauna Query Language) to integrate with Fauna.

我们将以现有的Faunadb Spring Web App文章为基础,其中我们用FQL(Fauna查询语言)编写了几个CRUD查询,以与Fauna集成。

Let’s add the query to create the Health data in Fauna; we will start with the keyword Create and mention the collection name for which we are inserting the data:

让我们添加查询,在Fauna中创建健康数据;我们将以关键词Create 开始,并提及我们要插入数据的集合名称。

Value queryResponse = faunaClient.query(
    Create(Collection("healthdata"), //)
    ).get();

Next, we will create the actual data object to be inserted. We will define the attributes of the object and its values as entries of a Map and wrap it using the FQL’s Value keyword:

接下来,我们将创建要插入的实际数据对象。我们将把对象的属性和它的值定义为Map的条目,并使用FQL的Value关键字来包装它。

Create(Collection("healthdata"), 
    Obj("data", 
        Obj(Map.of(
            "userId", Value(healthData.userId())))))
)

Here, we are reading the userId from the Health Data record and mapping it to the userId field in the document we are inserting.

这里,我们从健康数据记录中读取userId,并将其映射到我们要插入的文档中的userId字段。

Similarly, we can do for all the remaining attributes:

类似地,我们可以对所有其余的属性进行处理。

Create(Collection("healthdata"), 
    Obj("data", 
        Obj(Map.of(
            "userId", Value(healthData.userId()), 
            "temperature", Value(healthData.temperature()),
            "pulseRate", Value(healthData.pulseRate()),
            "bpSystolic", Value(healthData.bpSystolic()),
            "bpDiastolic", Value(healthData.bpDiastolic()),
            "latitude", Value(healthData.latitude()),
            "longitude", Value(healthData.longitude()),
            "timestamp", Now()))))

Finally, let’s log the response of the query so that we are aware of any issues during query execution:

最后,让我们记录下查询的响应,这样我们就能意识到查询执行过程中的任何问题。

log.info("Query response received from Fauna: {}", queryResponse);

Note: For the purpose of this article, we have built the Edge services as a Spring Boot application. In production, these services can be built in any language and deployed across the global network by Edge providers such as Fastly, Cloudflare Workers, Lambda@Edge etc.

注意。为了本文的目的,我们将Edge服务构建为Spring Boot应用程序。在生产中,这些服务可以用任何语言构建,并由Edge提供商在全球网络中部署,例如FastlyCloudflare WorkersLambda@Edge等等。

Our integration is complete; let’s test the entire flow with an integration test.

我们的集成已经完成;让我们用一个集成测试来测试整个流程。

8. Testing End-to-End Integration

8.测试端到端集成

Let’s add a test to verify that our integration is working end-to-end and our requests are going to the correct Fauna regions. We’ll mock the GeoLocationService to toggle the region for our tests:

让我们添加一个测试,以验证我们的集成是端到端的工作,我们的请求是去正确的Fauna区域。我们将模拟GeoLocationService,为我们的测试切换区域。

@SpringBootTest
class DefaultHealthServiceTest {

    @Autowired
    private DefaultHealthService defaultHealthService;

    @MockBean
    private GeoLocationService geoLocationService;
    
    // ...
} 

Let’s add a test for the EU region:

让我们为欧盟地区添加一个测试。

@Test
void givenEURegion_whenProcess_thenRequestSentToEURegion() {

    HealthData healthData = new HealthData("user-1-eu",
        37.5f,
        99f,
        120, 80,
        51.50, -0.07,
        ZonedDateTime.now());

    // ...
}

Next, let’s mock the region and call the process method:

接下来,让我们模拟这个区域并调用process方法。

when(geoLocationService.getRegion(51.50, -0.07)).thenReturn("EU");

defaultHealthService.process(healthData);

When we run the test, we can check in the logs that the correct URL was fetched to create the FaunaClient:

当我们运行测试时,我们可以在日志中检查是否获取了正确的URL来创建FaunaClient

Creating Fauna Client for Region:EU with URL:https://db.eu.fauna.com/

And we can also check the response returned from the Fauna server that confirms the record was created correctly:

而且我们还可以检查从Fauna服务器返回的响应,确认记录被正确创建。

Query response received from Fauna: 
{
ref: ref(id = "338686945465991375", 
collection = ref(id = "healthdata", collection = ref(id = "collections"))), 
ts: 1659255891215000, 
data: {bpDiastolic: 80, 
userId: "user-1-eu", 
temperature: 37.5, 
longitude: -0.07, latitude: 51.5, 
bpSystolic: 120, 
pulseRate: 99.0, 
timestamp: 2022-07-31T08:24:51.164033Z}}

We can also verify the same record in our Fauna dashboard under the HealthData collection for the EU database:

我们也可以在欧盟数据库的HealthData集合下的Fauna仪表板上验证相同的记录。

fauna dashboard collection

Similarly, we can add the test for the US region:

同样地,我们可以添加美国地区的测试。

@Test
void givenUSRegion_whenProcess_thenRequestSentToUSRegion() {

    HealthData healthData = new HealthData("user-1-us", //
        38.0f, //
        100f, //
        115, 85, //
        40.75, -74.30, //
        ZonedDateTime.now());

    when(geoLocationService.getRegion(40.75, -74.30)).thenReturn("US");

    defaultHealthService.process(healthData);
}

9. Conclusion

9.结语

In this article, we’ve explored how we can leverage Fauna’s Distributed, Document-relational and serverless features to use it as a database for IoT applications. Fauna’s Region groups infrastructure addresses locality concerns and mitigates the Edge servers’ latency issues.

在本文中,我们探讨了如何利用Fauna的分布式文档关系式和无服务器功能,将其作为IoT应用的数据库。Fauna的区域组基础设施解决了位置性问题,并缓解了边缘服务器的延迟问题。

You can also check Fauna’s on-demand webinar on how Fauna reduces latency in edge apps.

您还可以查看Fauna的按需网络研讨会,了解Fauna如何降低边缘应用程序的延迟。

All the code shown here is available over on Github.

这里显示的所有代码都可以在Github上找到