上QQ阅读APP看书,第一时间看更新
Cloud microservices aggregating the alerts
The primary goal of the Cloud Service is to store the Alerts data that is generated in the Edge Gateway, and the ability to retrieve the data for future access. In the previous sections have covered the Edge Gateway, and the Cloud connectivity with WebSockets on both the Edge Gateway and the Cloud Server. In this section, we will implement the alerts Cloud service and APIs. To implement the Alerts Service we used the following:
- Spring Boot to build out the REST API: Spring Boot applications are typically run in a cloud system such as Cloud Foundry or AWS/Azure and hence all the configurations have to be externalized
- REST end points to transfer JSON messages
- Used Liquibase to manage the DB connection
- Eclipse link to manage the DB access and as a Java Persistence Access (JPA)
We will start by adding out the dependencies, as can be seen in the following example:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.4.0</version>
</dependency>
- The Domain model is in the directory iiot-sample-domain folder.
- The Alerts model is given in the following example. It is wired to the Alerts table using JPA annotation. @Table points to the Alerts table and @column identifies the mapping of the Java Bean field to the table field.
- We are also using lombok @slf4j to add getters and setters boilerplate code to the bean, as follows:
@Entity
@EntityListeners({AuditContext.class})
@Data
@Table(name = "Alerts")
@Cacheable(value=false)
@Slf4j
public class Alert implements Serializable {
@Id
@Column(name = "ID", nullable=false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Long id;
@JsonProperty
@Column(name = "alerts_uuid")
private String alertsUuid;
@Column(name = "severity")
private int severity;
@Column(name = "alert_name")
private String alertName;
@Column(name = "alert_info")
private String alertInfo;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Column(name = "created_by")
private String createdBy;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Column(name = "updated_by")
private String updatedBy;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Column(name = "tenant_uuid")
private String tenantUuid;
- Persistence configuration provides the details of the persistence storage. The types of persistence engine we are using, such as Eclipselink, and detailed in the following example:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="iiotSamplePersistentUnit"
transaction-type="RESOURCE_LOCAL">
<description>Persistence Unit</description>
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<mapping-file>META-INF/custom-orm.xml</mapping-file>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.logging.level.sql" value="INFO"/>
<property name="eclipselink.weaving" value="static"/>
<property name="eclipselink.profiler" value="QueryMonitor"/> <!-- probably disable this in prod -->
<property name="eclipselink.jdbc.native-sql" value="true"/>
<property name="javax.persistence.query.timeout" value="10000"/>
</properties>
</persistence-unit>
</persistence>
- We will use the env property only when running locally. In a cloud deployment, we will use the manifest.yml to bind to appropriate PostgreSQL instances at cloud, but the code does not need to change to support various environments.
- Liquibase configuration and DDL scripts are given in the following example. We are reading from an env property file and Spring automatically binds such configurations as the driver and credentials without hardcoding one of the 12-factor concepts we discussed previously:
@Configuration
public class LiquibaseConfiguration {
private final DataSource dataSource;
@Value("${spring.datasource.driver-class-name:org.postgresql.Driver}")
private String dataSourceDriverClassName;
@Value("${vcap.services.${iiot_sample_postgres_name:iiot-sample-postgres}.credentials.uri}")
private String dataSourceUrl;
@Value("${vcap.services.${iiot_sample_postgres_name:iiot-sample-postgres}.credentials.username}")
private String dataSourceUsername;
@Value("${vcap.services.${iiot_sample_postgres_name:iiot-sample-postgres}.credentials.password}")
private String dataSourcePassword;
@Autowired
public LiquibaseConfiguration(DataSource dataSource){
this.dataSource = dataSource;
}
@Bean
public SpringLiquibase liquibase(TenantDataSourceConfig tenantDataSourceConfig) {
SmarterSpringLiquibase liquibase = new SmarterSpringLiquibase(tenantDataSourceConfig);
liquibase.setChangeLog("classpath:db/changelog.xml");
liquibase.setDataSource(dataSource);
liquibase.setDefaultSchema("iiot-sample");
liquibase.setDropFirst(false);
liquibase.setShouldRun(true);
return liquibase;
}
}
- DDL scripts are for Liquibase. All of the DB scripts are saved in the resources/db directory as follows:
CREATE TABLE alerts
(
id bigserial NOT NULL,
alerts_uuid text NOT NULL,
severity integer,
alert_name text,
alert_info text,
created_by text NOT NULL,
created_date timestamp with time zone NOT NULL DEFAULT now(),
updated_by text,
updated_date timestamp with time zone,
tenant_uuid text NOT NULL,
CONSTRAINT alerts_pkey PRIMARY KEY (id)
);
CREATE UNIQUE INDEX ALERTS_TENANT_IDX ON Alerts(alerts_uuid, tenant_uuid);
- Cloud applications should be multi-tenant and hence we added a tenant_uuid column. For now, we will use the default tenant but we can change it at any time by sending the tenant identifier as part of the header.
- We saw the DB layer, domain objects now we will move on to the repository layer, which we will leverage JPA heavily; AlertsRepository.java is given. It has two calls to get the list of alerts given a tenant and find an alert given a UUID. I have also used pageable to get the paginated list of alerts, as can be seen in the following example:
public interface AlertsRepository extends CrudRepository<Alert, Long> {
Alert findByAlertsUuidAndTenantUuid(String alertsUuid, String tenantUuid);
Page<Alert> findByTenantUuid(String tenantUuid,Pageable pageable);
}
- We will now look atIioTController which exposes the REST APIs to the outside world. This has two APIs, CreateAlert and GetAlertList. Creating the alert is done though the WebSocket controller, but we can leverage the same code here to add a RestEnd point for other clients who are not using the WebSockets:
@RestController
@RequestMapping("/v1/iiotsample")
@Slf4j
public class IiotController {
private static final Logger logger = LoggerFactory.getLogger(IiotController.class);
private final IiotService iiotService;
@Autowired
private TenantUtil tenantUtil;
@Autowired
public IiotController(IiotService iiotService) {
this.iiotService = iiotService;
}
@RequestMapping(value = "/alerts", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
public ResponseEntity<Page<Alert>> getListofAlerts(
@RequestHeader(value="Authorization",required=false) String authorization,
@RequestHeader(value="referer", required = false) String referer, HttpServletRequest request) {
logger.error("referer {}", referer);
StopWatch stopWatch = new StopWatch("getList of Alerts");
Credentials credentials = new Credentials(authorization, tenantUtil.getTenantUuid());
credentials.setReferer(referer);
Page<Alert> listofAlerts = iiotService.getAlerts(tenantUtil.getTenantUuid(),stopWatch);
logger.info("{}", new StopWatchUtil(stopWatch));
return new ResponseEntity<>(listofAlerts, HttpStatus.OK);
}
@RequestMapping(value = "/alert", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
public ResponseEntity<Alert> getAlert(
@RequestBody Alert alert,
@RequestHeader(value="Authorization",required=false) String authorization,
@RequestHeader(value="referer", required = false) String referer, HttpServletRequest request) {
logger.error("referer {}", referer);
StopWatch stopWatch = new StopWatch("Create Alert");
Credentials credentials = new Credentials(authorization, tenantUtil.getTenantUuid());
credentials.setReferer(referer);
Alert createdAlert = iiotService.createAlerts(alert,credentials,stopWatch);
logger.info("{}", new StopWatchUtil(stopWatch));
return new ResponseEntity<>(createdAlert, HttpStatus.CREATED);
}
}
- To test this application, I have created a simple Postman collection. Please do install Postman which is a chrome plugin and import the iiot-postman collection
- Run the application in server mode. This should launch the server application as follows:
./run_app_dev.sh
- Create alert and then get the list of alerts to see if the alert is created using the Postman collections.
Import the file iiot-cloud-alert-service.postman_collection ( https://github.com/tjayant/iiot-book-samples/blob/master/iiot-cloud-service/iiot-cloud-alert-service.postman_collection) file to the Postman tool.
- Issue the createAlert operation.
- Issue the List alert operation. You should see the list of alerts in the Postman window.
- Now let us run the system end to end.
- Run the IIot-Edge-Gateway in a new terminal, as can be seen in the following example:
node iiot-edge.js
- Issue the List alert operation. You should see the list of alerts in the Postman window.
- You should see four alerts created (as given in the following example) once you run the applications, which show that the application is running successfully.
- This wraps up our sample IIoT application and all of the code as follows:
{
"content": [ {
"alertsUuid": "AlertUUid2017-09-25 08:44:07",
"severity": 1,
"alertName": "Tempreture Alert",
"alertInfo": "Tempreture Value:50"
}, {
"alertsUuid": "AlertUUid2017-09-25 09:33:55",
"severity": 1,
"alertName": "Tempreture Alert",
"alertInfo": "Tempreture Value:150"
}],
"totalPages": 1,
"totalElements": 3,
"last": false,
"size": 10,
"number": 0,
"numberOfElements": 10,
"sort": null,
"first": true
}