AxonFramework와 Kafka를 이용한 게시판 서비스 구현 (feat Spring Boot)

Kafka 및 axsonFramework 개발 환경을 설정하고 Spring Boot에서 간단한 게시판 서비스를 구현하는 방법

1. 개발 환경 설정

  • Java JDK 8 이상이 설치되어 있는지 확인
  • Eclipse 또는 IntelliJ와 같은 통합 개발 환경(IDE) 설치
  • 종속성 관리를 위해 Apache Maven 설치

2. 스프링 부트 프로젝트 생성

  • 스프링 시작 프로젝트 만들기
  • pom.xml에서 Web, Kafka, AxonFramework 및 JPA(또는 선택한 다른 데이터베이스)를 사용하기 위한 종속성을 추가합니다.

이 pom.xml 파일은 게시판 애플리케이션에 필요한 종속성과 함께 Spring Boot 프로젝트를 설정합니다.
여기에는 다음과 같은 주요 종속성이 포함됩니다.

  • spring-boot-starter-web: REST API 지원으로 웹 애플리케이션 구축용.
  • spring-boot-starter-data-jpa: JPA 및 Hibernate를 사용한 데이터 액세스 관리용.
  • axon-spring-boot-starter: AxonFramework를 Spring Boot와 통합합니다.
  • spring-kafka: Kafka를 Spring Boot에 통합하기 위한 것입니다.
  • h2: 개발용 인메모리 데이터베이스.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.5</version>
		<relativePath/> <!
-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>axonProject</artifactId> <version>0.0.1-SNAPSHOT</version> <name>axonProject</name> <description>Demo project for Spring Boot</description> <properties> <java.version>19</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.axonframework</groupId> <artifactId>axon-spring-boot-starter</artifactId> <version>4.7.3</version> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
  • application.properties(src/main/resources 폴더에서 application.properties에서)
# Server configuration
server.port=8080

# Kafka configuration
spring.kafka.consumer.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=bulletin-board-group
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.producer.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer

# Axon configuration
axon.axonserver.servers=localhost:8124

3. 게시판 디자인

  • 다음과 같은 응용 프로그램의 주요 구성 요소를 정의합니다.
    B. 집계, 명령, 이벤트 및 쿼리 모델.
  • AxonFramework로 명령 및 이벤트 처리기를 구현합니다.
  • Spring Kafka를 사용하여 뉴스를 생산하고 소비합니다.

4. 게시판 구현

  • 예를 들어 포럼의 게시물을 나타내는 새로운 집계 클래스입니다.
    우편 집계).
  • 게시물 생성 및 업데이트를 위한 명령 및 이벤트 클래스. CreatePostCommand, PostCreatedEvent).
  • 집계 내에서 명령 및 이벤트 처리기를 구현합니다.
    @커맨드핸들러, @이벤트 핸들러).
  • 프로젝션 클래스(예: PostProjection) 이벤트를 기반으로 쿼리 모델을 업데이트합니다.
  • 쿼리 모델을 지속하기 위한 저장소, 예. 포스트리포지토리).
  • 다음과 같은 REST API PostController)를 사용하여 애플리케이션의 기능을 노출합니다.
// PostAggregate.java
@Aggregate
public class PostAggregate {

    @AggregateIdentifier
    private String id;
    private String title;
    private String content;

    @CommandHandler
    public PostAggregate(CreatePostCommand command) {
        apply(new PostCreatedEvent(command.getId(), command.getTitle(), command.getContent()));
    }

    @EventHandler
    public void on(PostCreatedEvent event) {
        this.id = event.getId();
        this.title = event.getTitle();        
        this.content = event.getContent();
    }

    protected PostAggregate() {
        // For Axon Framework
    }
}

// CreatePostCommand.java
public class CreatePostCommand {

    private final String id;
    private final String title;
    private final String content;

    public CreatePostCommand(String id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
    }

    // Getters ...
}
// PostProjection.java
@Component
public class PostProjection {

    private final PostRepository postRepository;

    public PostProjection(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @EventHandler
    public void on(PostCreatedEvent event) {
        Post post = new Post(event.getId(), event.getTitle(), event.getContent());
        postRepository.save(post);
    }
}
// Post.java (Entity)
@Entity
public class Post {

    @Id
    private String id;
    private String title;
    private String content;

    public Post(String id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
    }

    // Getters and setters ...
}
// PostRepository.java
public interface PostRepository extends JpaRepository<Post, String> {
}
// PostController.java
@RestController
@RequestMapping("/api/posts")
public class PostController {

    private final CommandGateway commandGateway;
    private final PostRepository postRepository;

    public PostController(CommandGateway commandGateway, PostRepository postRepository) {
        this.commandGateway = commandGateway;
        this.postRepository = postRepository;
    }

    @PostMapping
    public CompletableFuture<String> createPost(@RequestBody PostRequest postRequest) {
        String postId = UUID.randomUUID().toString();
        return commandGateway.send(new CreatePostCommand(postId, postRequest.getTitle(), postRequest.getContent()));
    }

    @GetMapping
    public List<Post> getAllPosts() {
        return postRepository.findAll();
    }
}
// PostRequest.java
public class PostRequest {

    private String title;
    private String content;

    // Getters and setters ...
}

5. 게시판 프로젝트 실행

  • 카프카 시작
  • Kafka가 설치되어 있지 않은 경우(https://kafka.apache.org/) Kafka를 다운로드하고 압축을 풉니다.
  • 터미널을 실행하고 Kafka 설치 폴더로 이동합니다.
  • maxOS용(bin/zookeeper-server-start.sh config/zookeeper.properties)
  • 결승 2
  • maxOS용(bin/kafka-server-start.sh config/server.properties)
  • Spring Boot 애플리케이션을 시작합니다(예: 기본 클래스를 실행하거나 Maven의 경우 ./mvnw spring-boot:run 또는 Gradle의 경우 ./gradlew bootRun 사용).
  • Postman 또는 CURL과 같은 REST 클라이언트를 사용하여 게시물을 만들고 검색하기 위한 /api/posts 끝점을 테스트합니다.

6. 축삭

  • AxonServer 다운로드: 공식 웹 사이트에서 최신 버전의 AxonServer를 다운로드합니다.
    https://axoniq.io/product-overview/axon-server. “Axon Server Standard Edition”을 선택하고 .jar 파일을 다운로드합니다.
  • AxonServer 실행: 터미널(또는 Windows의 명령 프롬프트)을 열고 AxonServer JAR 파일을 다운로드한 디렉터리로 이동합니다.
    다음 명령을 실행하여 AxonServer를 시작합니다.
java -jar axonserver.jar

기본적으로 AxonServer는 포트 8124에서 수신 대기합니다.
게시판 응용 프로그램이 작동하려면 AxonServer가 백그라운드에서 실행되어야 하므로 터미널을 실행 상태로 유지하십시오.

애플리케이션 속성 업데이트: application.properties 파일의 axon.axonserver.servers 속성이 올바른 AxonServer 주소 및 포트(기본적으로 localhost:8124)를 가리키는지 확인하십시오. 아직 추가하지 않은 경우 application.properties 파일에 다음 행을 추가하십시오(삭제했다면 이미 추가하십시오).