使用Spring Boot后端和Angular构建TodoMVC并部署到Kubernetes

本文详细介绍了如何构建一个结合Angular前端和Spring Boot后端的TodoMVC应用,包括实体定义、控制器实现、数据库配置,并通过Docker镜像构建和OpenShift Operator完成Kubernetes部署。

Angular TodoMVC with Spring boot backend, deploy to Kubernetes

本文指导您构建一个Spring Boot演示应用,并使用JBoss Web Server Operator将其部署到Kubernetes上。该应用使用TodoMVC Angular前端,并与Spring Boot后端集成。Todo实体使用JPA注解进行数据库映射。TodoController处理CRUD操作,TodoRepository扩展JpaRepository用于数据库交互。应用可以在本地运行,也可以使用Tomcat作为嵌入式服务器打包。部署过程包括构建Docker镜像、推送到仓库以及在OpenShift上部署。提供了H2和PostgreSQL数据库的配置细节。

应用架构

我们使用TodoMVC Angular应用。TodoMVC是一个项目,提供使用各种流行JavaScript框架实现的相同“Todo”应用。TodoMVC的目标是通过展示不同框架如何用相同功能解决相同问题,帮助开发人员比较和对比不同框架和库。在这个演示应用中,我们使用TodoMVC前端Angular示例。项目构建后复制到Java项目的/resources目录中,以保持在同一应用和根上下文中。

后端使用Spring Boot实现,这是一个旨在简化独立、生产级基于Spring的应用开发的框架。它通过在Spring Framework之上提供预配置模板和约定优于配置的方法,使应用设置和部署更加容易,配置最少。

该项目使用Spring Boot框架和Spring Boot Tomcat Starter。这是一个依赖项,您可以包含在Spring Boot项目中,以使用嵌入式Apache Tomcat服务器运行应用。这允许您将应用打包为包含Tomcat的自包含可执行JAR文件,使应用部署和运行更加容易,无需配置外部Web服务器。

完整应用源代码可在此处获取。

Todo实体

首先创建一个名为Todo的实体。这是我们希望将数据存储到后端数据库的方式。

Todo表示数据库中“待办事项”的实体。该类使用Jakarta Persistence(前身为Java Persistence API或JPA)注解将类映射到数据库表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Entity
@Table(name = "todos")
public class Todo {

    // 指定主键由数据库自动生成(自增)。
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 确保标题字段不为空(不能为null或空),且唯一。
    @NotBlank
    @Column(unique = true)
    private String title;

    private boolean completed;

    // 将字段映射到数据库中的“ordering”列。
    @Column(name = "ordering")
    private int order;

    @Column(name = "url")
    private String url;

    // Getter和Setter方法
    ....
}

TODO控制器

TodoController处理与“Todo”项相关的HTTP请求。它利用多个Spring组件和注解来定义CRUD(创建、读取、更新、删除)操作的端点。

控制器定义以下结构:

  • @RestController:标记此类为Spring MVC控制器,其中每个方法返回域对象而非视图。它结合了@Controller@ResponseBody
  • @CrossOrigin:为整个控制器启用跨源资源共享(CORS),允许来自不同源的请求。
  • @RequestMapping("/api/todos"):将HTTP请求映射到/api/todos到此控制器。
1
2
3
4
5
6
7
@RestController
@CrossOrigin
@RequestMapping("/api/todos")
public class TodoController {

    @Autowired
    private TodoService todoService;

获取所有Todos

将HTTP GET请求映射到此方法。它通过调用todoService.getAllTodos()返回所有待办事项的列表。

1
2
3
4
    @GetMapping
    public List<Todo> getAllTodos() {
        return todoService.getAllTodos();
    }

按ID获取Todo

@GetMapping("/{id}"):将带有路径变量id的HTTP GET请求映射到此方法。它通过ID检索待办事项。

@PathVariable Long id:将id路径变量绑定到方法参数。

ResponseEntity:用适当的HTTP状态码包装响应。如果找到待办事项,返回200 OK,否则返回404 Not Found。

1
2
3
4
5
    @GetMapping("/{id}")
    public ResponseEntity<Todo> getTodoById(@PathVariable Long id) {
        Optional<Todo> todo = todoService.getTodoById(id);
        return todo.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }

创建Todo

要创建Todo,我们期望从前端收到带有JSON的请求。

@Transactional:确保方法在事务内执行。

@PostMapping:将HTTP POST请求映射到此方法。它创建一个新的待办事项。

@RequestBody Todo todo:将请求体绑定到方法参数todo

1
2
3
4
5
    @Transactional
    @PostMapping
    public Todo createTodo(@RequestBody Todo todo) {
        return todoService.createOrUpdateTodo(todo);
    }

更新Todo

@PutMapping("/{id}"):将带有路径变量id的HTTP PUT请求映射到此方法。它更新现有的待办事项。

@RequestBody Todo todoDetails:将请求体绑定到方法参数todoDetails

ResponseEntity:如果更新成功,返回200 OK,否则返回404 Not Found。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    @Transactional
    @PutMapping("/{id}")
    public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody Todo todoDetails) {
        Optional<Todo> todo = todoService.getTodoById(id);
        if (todo.isPresent()) {
            Todo todoToUpdate = todo.get();
            todoToUpdate.setTitle(todoDetails.getTitle());
            todoToUpdate.setCompleted(todoDetails.isCompleted());
            todoToUpdate.setOrder(todoDetails.getOrder());
            todoToUpdate.setUrl(todoDetails.getUrl());
            return ResponseEntity.ok(todoService.createOrUpdateTodo(todoToUpdate));
        } else {
            return ResponseEntity.notFound().build();
        }
    }

删除Todo

@DeleteMapping("/{id}"):将带有路径变量id的HTTP DELETE请求映射到此方法。它通过ID删除待办事项。

ResponseEntity<Void>:如果删除成功,返回204 No Content。

1
2
3
4
5
6
7
    @Transactional
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteTodoById(@PathVariable Long id) {
        todoService.deleteTodoById(id);
        return ResponseEntity.noContent().build();
    }
}

Todo仓库

通过扩展JpaRepositoryTodoRepository继承了多个执行常见数据库操作的方法,无需显式定义它们:

  • save(S entity):保存给定实体。
  • findById(ID id):通过ID检索实体。
  • findAll():检索所有实体。
  • deleteById(ID id):通过ID删除实体。
  • deleteAll():删除所有实体。

@Repository注解表明该接口是Spring Data仓库。

1
2
3
4
5
6
7
8
9
package org.acme.todo.repository;

import org.acme.todo.model.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
}

本地运行应用

最后定义数据库配置。要运行后端数据库,或者您可以在application.properties中取消注释H2数据库设置。(在这种情况下,确保注释掉PG属性)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
## 默认连接池
spring.datasource.hikari.connectionTimeout=20000
spring.datasource.hikari.maximumPoolSize=5

# H2
#spring.datasource.url=jdbc:h2:mem:testdb
#spring.datasource.driverClassName=org.h2.Driver
#spring.datasource.username=sa
#spring.datasource.password=password
#spring.h2.console.enabled=true
#spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
#spring.jpa.hibernate.ddl-auto=update

## PostgreSQL
spring.datasource.url=jdbc:postgresql://todos-database:5432/todos
spring.datasource.username=jws
spring.datasource.password=jws
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

要为后端启动PG数据库,我使用podman(容器运行时):

1
podman run --name todos-database -p 5432:5432 -e POSTGRES_USER=jws -e POSTGRES_PASSWORD=jws -e POSTGRES_DB=todos -d postgres:15-alpine

从源代码运行应用。

命令mvn spring-boot:run用于使用Apache Maven运行Spring Boot应用。

mvn spring-boot:run命令是一种方便的方式,可以直接从命令行启动Spring Boot应用,无需先将其打包为JAR或WAR文件。它通常在开发阶段用于快速测试和迭代应用。

1
mvn spring-boot:run

构建镜像并推送到仓库

首先需要构建和打包源文件。

1
mvn clean compile package

该命令由Maven构建生命周期的三个独立阶段组成:clean、compile和package。正如目标所述,命令完成后,应将WAR文件复制到target目录中。准备部署到JBoss Web Server(JWS)。

JWS Operator可以使用预构建的镜像,也可以从源代码派生一个。在此示例中,我们创建一个镜像并将其推送到quay.io。

首先简单介绍Dockerfile:

1
2
3
FROM registry.redhat.io/jboss-webserver-6/jws60-openjdk17-openshift-rhel8:6.0.2-2

COPY target/todo-demo-jws-0.0.1-SNAPSHOT.war /deployments/ROOT.war

我们使用JBoss Web Server 6镜像。镜像使用OpenJDK版本17。

我们还将war文件复制到根目录,以便在/上解析。

运行以下命令构建镜像:

1
podman build --arch=x86_64 -t YOUR_REPO_NAME:latest .
  • podman build:这是使用Podman构建容器镜像的主要命令。类似于Docker中的docker build
  • –arch=x86_64:这指定容器镜像的架构。x86_64表示镜像应为64位x86架构构建。当您为不同架构构建镜像并需要明确指定目标架构时,这非常有用。例如,如果您在Apple Silicon如M系列上运行构建,这很有用。
  • -t quay.io/sshaaf/todo-demo-jws:latest:例如,-t用于标签,其余部分是仓库名称/镜像。

构建完成后,将镜像推送到OpenShift集群可访问的仓库。Quay.io或DockerHub都可以被OpenShift集群访问,因为它们是公开托管的。

使用Operator在OpenShift上运行应用

首先,在部署应用之前需要部署数据库。

数据库

以下命令使用最新的PostgreSQL镜像在OpenShift中创建新的PostgreSQL数据库应用。它设置数据库用户名为jws,密码为jws,数据库名为todos。应用命名为todos-database

1
2
3
4
5
oc new-app -e POSTGRESQL_USER=jws \
            -e POSTGRESQL_PASSWORD=jws \
            -e POSTGRESQL_DATABASE=todos \
            openshift/postgresql:latest \
            --name=todos-database
  • oc new-app:这是在OpenShift中创建新应用的主要命令。它帮助从源代码、模板或Docker镜像实例化新应用。
  • -e POSTGRESQL_USER=jws:此标志设置环境变量POSTGRESQL_USER,值为jws。在此上下文中,用于指定PostgreSQL数据库的用户名。
  • -e POSTGRESQL_PASSWORD=jws:类似于前一个标志,这设置另一个环境变量POSTGRESQL_PASSWORD,值为jws。这是PostgreSQL用户的密码。
  • -e POSTGRESQL_DATABASE=todos:这设置环境变量POSTGRESQL_DATABASE,值为todos,指定要创建的PostgreSQL数据库的名称。
  • openshift/postgresql:latest:这部分指定用于新应用的Docker镜像。openshift/postgresql:latest表示应使用OpenShift仓库中最新版本的PostgreSQL镜像。
  • –name=todos-database:此选项为正在创建的新应用分配名称。在这种情况下,应用将命名为todos-database

Operator

有多种安装Operator的方法。Operator可通过OperatorHub在OpenShift控制台中搜索和安装。

创建新的Webserver实例

安装Operator后,可以使用以下CR安装应用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: web.servers.org/v1alpha1
kind: WebServer
metadata:
  name: todo-demo-app
  namespace: test
spec:
  webImage:
    applicationImage: 'quay.io/sshaaf/todo-demo-jws'
  applicationName: jws-app
  replicas: 2
  • apiVersion: web.servers.org/v1alpha1:这指定资源的API版本。在这种情况下,它是web.servers.org/v1alpha1,表明此资源是自定义API组web.servers.org的一部分,版本为v1alpha1
  • kind: WebServer:这定义所描述资源的类型。此处是WebServer。这可能是Kubernetes集群中CRD定义的自定义资源。
  • metadata:此部分包含资源的元数据。
    • name: todo-demo-app:这将资源名称设置为todo-demo-app
    • namespace: test:这指定资源将在test命名空间中创建。
  • spec:此部分定义资源的期望状态。
    • webImage:此嵌套部分可能涉及Web服务器的镜像设置。
      • applicationImage: ‘quay.io/sshaaf/todo-demo-jws’:这指定用于应用的Docker镜像。它指向quay.io/sshaaf/todo-demo-jws,这是一个托管的镜像仓库(Quay.io)。
    • applicationName: jws-app:这将应用名称设置为jws-app
    • replicas: 2:这指定要运行的应用副本(实例)的期望数量。在这种情况下,请求2个副本。

运行以下命令获取部署应用的路由:

1
oc get routes

输出示例:

1
2
NAME      HOST/PORT                            PATH   SERVICES   PORT    TERMINATION   WILDCARD
jws-app   jws-app-xx.apps.red.demoshift.com          jws-app    <all>                 None

您现在可以使用运行在JWS上的出色TODO应用,使用Spring Boot 3和AngularJS。

更多配置选项

  • replicas:这指定要运行的应用副本(实例)的期望数量。
  • useSessionClustering: true:启用DNSping会话集群。
  • 通过Webhooks部署:JWS文档。

← 上一篇 下一篇 →

目录

  • Todo实体
  • TODO控制器
  • 获取所有Todos
  • 按ID获取Todo
  • 创建Todo
  • 更新Todo
  • 删除Todo
  • Todo仓库
  • 本地运行应用
  • 构建镜像并推送到仓库
  • 使用Operator在OpenShift上运行应用
  • 数据库
  • Operator
  • 创建新的Webserver实例

特色标签

administrator, ant, automation, build, cache, ci, command, computers, continous, design, design-patterns, docker, engineering, fedora, gof, how-to, howto, ibm, infinispan, integration, jacl, java, jdbc, jdk-21, jython, kubernetes, llm, migration, mq, openjdk, openshift, patterns, programming, quarkus, redhat, release, rhel, scm, scripting, singleton, singleton-pattern, software, software-development, svn, sysadmin, tips, tools, utils, websphere, wsadmin

版权所有 © Shaaf’s blog 2025

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计