JAVA

AxonFramework와 kafka를 활용한 게시판 서비스 구현하기.(feat Spring Boot)

wooyit 2023. 3. 24. 02:36

Spring Boot에서 kafka 및 axsonFramework 개발환경을 셋팅하고 간단한 게시판 서비스 구현하는 방법

 

1. 개발환경 설정

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

2. Spring Boot 프로젝트 생성

  • Spring Start Project를 생성
  • 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. 게시판 설계

  • 집계, 명령, 이벤트 및 쿼리 모델과 같은 애플리케이션의 주요 구성 요소를 정의합니다.
  • AxonFramework를 사용하여 명령 및 이벤트 핸들러를 구현합니다.
  • Spring Kafka를 사용하여 메시지를 생성하고 소비합니다.

4. 게시판 구현

  • 게시판의 게시물을 나타내는 새로운 집계 클래스(예: PostAggregate)를 만듭니다.
  • 게시물을 만들고 업데이트하기 위한 명령 및 이벤트 클래스(예: CreatePostCommand, PostCreatedEvent)를 구현합니다.
  • 집계 내에서 명령 및 이벤트 처리기를 구현합니다(예: @CommandHandler, @EventHandler).
  • 프로젝션 클래스(예: PostProjection)를 구현하여 이벤트를 기반으로 쿼리 모델을 업데이트합니다.
  • 쿼리 모델을 유지하기 위한 저장소(예: PostRepository)를 만듭니다.
  • 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 시작
  • 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. axon

  • 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 파일에 다음 행을 추가하십시오.(이미 추가하였음, 혹시 지웠다면 추가)