1. 개요

이 기사에서는 Eclipse MicroProfile을 기반으로 마이크로 서비스를 구축하는 데 중점을 둘 것입니다.

JAX-RS, CDI 및 JSON-P API를 사용하여 RESTful 웹 애플리케이션을 작성하는 방법을 살펴보겠습니다.

2. 마이크로서비스 아키텍처

간단히 말해서 마이크로서비스는 여러 독립 서비스의 모음으로 완전한 시스템을 형성하는 소프트웨어 아키텍처 스타일입니다.

각각은 하나의 기능적 경계에 초점을 맞추고 REST와 같은 언어 독립적 프로토콜을 사용하여 다른 경계와 통신합니다.

3. 이클립스 마이크로프로파일

Eclipse MicroProfile은 마이크로서비스 아키텍처용 엔터프라이즈 Java 최적화를 목표로 하는 이니셔티브입니다. Jakarta EE WebProfile API의 하위 집합을 기반으로 하므로 Jakarta EE 애플리케이션을 구축하는 것처럼 MicroProfile 애플리케이션을 구축할 수 있습니다.

MicroProfile의 목표는 마이크로서비스 구축을 위한 표준 API를 정의하고 여러 MicroProfile 런타임에 이식 가능한 애플리케이션을 제공하는 것입니다.

4. 메이븐 의존성

Eclipse MicroProfile 애플리케이션을 빌드하는 데 필요한 모든 의존성은 다음 BOM(Bill Of Materials) 의존성에 의해 제공됩니다.

<dependency>
    <groupId>org.eclipse.microprofile</groupId>
    <artifactId>microprofile</artifactId>
    <version>1.2</version>
    <type>pom</type>
    <scope>provided</scope>
</dependency>

MicroProfile 런타임에는 이미 API 및 구현이 포함되어 있으므로 범위는 제공된 대로 설정됩니다.

5. 표현 모델

빠른 리소스 클래스를 만들어 시작해 보겠습니다.

public class Book {
    private String id;
    private String name;
    private String author;
    private Integer pages;
    // ...
}

보시다시피 이 Book 클래스에는 어노테이션이 없습니다.

6. CDI 사용

간단히 말해서 CDI는 의존성 주입 및 수명 주기 관리를 제공하는 API입니다. 웹 애플리케이션에서 Enterprise Bean의 사용을 단순화합니다.

이제 책 표현을 위한 저장소로 CDI 관리 빈을 생성해 보겠습니다.

@ApplicationScoped
public class BookManager {

    private ConcurrentMap<String, Book> inMemoryStore
      = new ConcurrentHashMap<>();

    public String add(Book book) {
        // ...
    }

    public Book get(String id) {
        // ...
    }

    public List getAll() {
        // ...
    }
}

모든 클라이언트가 상태를 공유하는 인스턴스가 하나만 필요하기 때문에 @ApplicationScoped 로 이 클래스에 어노테이션을 추가합니다 . 이를 위해 ConcurrentMap을 형식이 안전한 메모리 내 데이터 저장소로 사용했습니다. 그런 다음 CRUD 작업을 위한 메서드를 추가했습니다 .

이제 우리 빈은 CDI 준비가 되어 있으며 @Inject 어노테이션 을 사용하여 빈 BookEndpoint 에 주입할 수 있습니다 .

7. JAX-RS API

JAX-RS로 REST 애플리케이션을 생성하려면 @ApplicationPath 로 어노테이션이 달린 애플리케이션 클래스 와 @Path로 어노테이션이 달린 리소스를 생성해야 합니다 .

7.1. JAX RS 애플리케이션

JAX-RS 애플리케이션은 웹 애플리케이션에서 리소스를 노출하는 기본 URI를 식별합니다.

다음 JAX-RS 애플리케이션을 생성해 보겠습니다.

@ApplicationPath("/library")
public class LibraryApplication extends Application {
}

이 예제에서 웹 애플리케이션의 모든 JAX-RS 자원 클래스는 LibraryApplication과 연관되어 동일한 라이브러리 경로(ApplicationPath 어노테이션의 값) 아래에 있도록 합니다 .

어노테이션이 달린 이 클래스는 JAX RS 런타임에 리소스를 자동으로 찾아 노출해야 한다고 알려줍니다.

7.2. JAX RS Endpoints

Resource 클래스라고도 하는 Endpoint 클래스는 기술적 으로 동일한 유형이 많이 가능하지만 하나의 리소스를 정의해야 합니다.

@Path 로 어노테이션이 달린 각 Java 클래스 또는 @Path 또는 @HttpMethod 로 어노테이션이 달린 하나 이상의 메서드가 있는 각 Java 클래스는 엔드포인트입니다.

이제 해당 표현을 노출하는 JAX-RS 엔드포인트를 생성합니다.

@Path("books")
@RequestScoped
public class BookEndpoint {

    @Inject
    private BookManager bookManager;
 
    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getBook(@PathParam("id") String id) {
        return Response.ok(bookManager.get(id)).build();
    }
 
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAllBooks() {
        return Response.ok(bookManager.getAll()).build();
    }
 
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response add(Book book) {
        String bookId = bookManager.add(book);
        return Response.created(
          UriBuilder.fromResource(this.getClass())
            .path(bookId).build())
            .build();
    }
}

이 시점에서 웹 애플리케이션의 /library/books 경로 아래에 있는 BookEndpoint 리소스 에 액세스할 수 있습니다 .

7.3. JAX RS JSON 미디어 유형

JAX RS는 REST 클라이언트와 통신하기 위해 많은 미디어 유형을 지원하지만 Eclipse MicroProfile은 JSOP-P API 사용을 지정하므로 JSON 사용을 제한합니다 . 따라서 메서드에 @Consumes(MediaType.APPLICATION_JSON) 및 @ Produces(MediaType.APPLICATION_JSON)로 어노테이션을 추가해야 합니다.

@Consumes 어노테이션은 허용되는 형식을 제한합니다. 이 예에서는 JSON 데이터 형식만 허용됩니다 . HTTP 요청 헤더 Content-Type은 application/json 이어야 합니다 .

동일한 아이디어가 @Produces 어노테이션 뒤에 있습니다. JAX RS Runtime은 응답을 JSON 형식으로 마샬링해야 합니다. 요청 HTTP 헤더 Accept는 application/json 이어야 합니다 .

8. JSON-P

JAX RS Runtime은 즉시 JSON-P를 지원하므로 JsonObject를 메서드 입력 매개변수 또는 반환 유형으로 사용할 수 있습니다.

그러나 실제 세계에서는 종종 POJO 클래스로 작업합니다. 따라서 JsonObject 와 POJO 간의 매핑을 수행할 방법이 필요합니다 . 여기에서 JAX RS 엔티티 Provider가 작동합니다.

JSON 입력 스트림을 Book POJO 로 마샬링하려면 Book 형식의 매개 변수를 사용하여 리소스 메서드를 호출하고 BookMessageBodyReader 클래스를 생성해야 합니다 .

@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class BookMessageBodyReader implements MessageBodyReader<Book> {

    @Override
    public boolean isReadable(
      Class<?> type, Type genericType, 
      Annotation[] annotations, 
      MediaType mediaType) {
 
        return type.equals(Book.class);
    }

    @Override
    public Book readFrom(
      Class type, Type genericType, 
      Annotation[] annotations,
      MediaType mediaType, 
      MultivaluedMap<String, String> httpHeaders, 
      InputStream entityStream) throws IOException, WebApplicationException {
 
        return BookMapper.map(entityStream);
    }
}

BookMessageBodyWriter 를 생성하여 반환 유형이 Book 인 리소스 메서드를 호출하는 Book to JSON 출력 스트림을 언마샬링하는 동일한 프로세스를 수행합니다 .

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class BookMessageBodyWriter 
  implements MessageBodyWriter<Book> {
 
    @Override
    public boolean isWriteable(
      Class<?> type, Type genericType, 
      Annotation[] annotations, 
      MediaType mediaType) {
 
        return type.equals(Book.class);
    }
 
    // ...
 
    @Override
    public void writeTo(
      Book book, Class<?> type, 
      Type genericType, 
      Annotation[] annotations, 
      MediaType mediaType, 
      MultivaluedMap<String, Object> httpHeaders, 
      OutputStream entityStream) throws IOException, WebApplicationException {
 
        JsonWriter jsonWriter = Json.createWriter(entityStream);
        JsonObject jsonObject = BookMapper.map(book);
        jsonWriter.writeObject(jsonObject);
        jsonWriter.close();
    }
}

BookMessageBodyReaderBookMessageBodyWriter는 @Provider 로 어노테이션 처리되므로 JAX RS 런타임에 의해 자동으로 등록됩니다.

9. 애플리케이션 구축 및 실행

MicroProfile 애플리케이션은 이식 가능하며 준수하는 모든 MicroProfile 런타임에서 실행되어야 합니다. Open Liberty 에서 애플리케이션을 빌드하고 실행하는 방법을 설명 하지만 호환되는 모든 Eclipse MicroProfile을 사용할 수 있습니다.

구성 파일 server.xml을 통해 Open Liberty 런타임을 구성합니다 .

<server description="OpenLiberty MicroProfile server">
    <featureManager>
        <feature>jaxrs-2.0</feature>
        <feature>cdi-1.2</feature>
        <feature>jsonp-1.0</feature>
    </featureManager>
    <httpEndpoint httpPort="${default.http.port}" httpsPort="${default.https.port}"
      id="defaultHttpEndpoint" host="*"/>
    <applicationManager autoExpand="true"/>
    <webApplication context-root="${app.context.root}" location="${app.location}"/>
</server>

플러그인 liberty-maven-plugin을 pom.xml에 추가해 보겠습니다 .

<?xml version="1.0" encoding="UTF-8"?>
<plugin>
    <groupId>net.wasdev.wlp.maven.plugins</groupId>
    <artifactId>liberty-maven-plugin</artifactId>
    <version>2.1.2</version>
    <configuration>
        <assemblyArtifact>
            <groupId>io.openliberty</groupId>
            <artifactId>openliberty-runtime</artifactId>
            <version>17.0.0.4</version>
            <type>zip</type>
        </assemblyArtifact>
        <configFile>${basedir}/src/main/liberty/config/server.xml</configFile>
        <packageFile>${package.file}</packageFile>
        <include>${packaging.type}</include>
        <looseApplication>false</looseApplication>
        <installAppPackages>project</installAppPackages>
        <bootstrapProperties>
            <app.context.root>/</app.context.root>
            <app.location>${project.artifactId}-${project.version}.war</app.location>
            <default.http.port>9080</default.http.port>
            <default.https.port>9443</default.https.port>
        </bootstrapProperties>
    </configuration>
    <executions>
        <execution>
            <id>install-server</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>install-server</goal>
                <goal>create-server</goal>
                <goal>install-feature</goal>
            </goals>
        </execution>
        <execution>
            <id>package-server-with-apps</id>
            <phase>package</phase>
            <goals>
                <goal>install-apps</goal>
                <goal>package-server</goal>
            </goals>
        </execution>
    </executions>
</plugin>

이 플러그인은 속성 집합을 구성할 수 있습니다.

<properties>
    <!--...-->
    <app.name>library</app.name>
    <package.file>${project.build.directory}/${app.name}-service.jar</package.file>
    <packaging.type>runnable</packaging.type>
</properties>

위의 exec 목표는 실행 가능한 jar 파일을 생성하므로 애플리케이션이 독립적으로 배포 및 실행할 수 있는 독립적인 마이크로 서비스가 됩니다. Docker 이미지로 배포할 수도 있습니다.

실행 가능한 jar을 만들려면 다음 명령을 실행합니다.

mvn package

마이크로서비스를 실행하기 위해 다음 명령을 사용합니다.

java -jar target/library-service.jar

그러면 Open Liberty 런타임이 시작되고 서비스가 배포됩니다. 다음 URL에서 엔드포인트에 액세스하고 모든 책을 가져올 수 있습니다.

curl http://localhost:9080/library/books

결과는 JSON입니다.

[
  {
    "id": "0001-201802",
    "isbn": "1",
    "name": "Building Microservice With Eclipse MicroProfile",
    "author": "baeldung",
    "pages": 420
  }
]

한 권의 책을 얻으려면 다음 URL을 요청합니다.

curl http://localhost:9080/library/books/0001-201802

결과는 JSON입니다.

{
    "id": "0001-201802",
    "isbn": "1",
    "name": "Building Microservice With Eclipse MicroProfile",
    "author": "baeldung",
    "pages": 420
}

이제 API와 상호 작용하여 새 책을 추가합니다.

curl 
  -H "Content-Type: application/json" 
  -X POST 
  -d '{"isbn": "22", "name": "Gradle in Action","author": "baeldung","pages": 420}' 
  http://localhost:9080/library/books

보시다시피 응답 상태는 201로 책이 성공적으로 생성되었음을 나타내며 Location은 책에 액세스 할 수 있는 URI입니다.

< HTTP/1.1 201 Created
< Location: http://localhost:9080/library/books/0009-201802

10. 결론

이 기사에서는 JAX RS, JSON-P 및 CDI에 대해 논의하면서 Eclipse MicroProfile을 기반으로 간단한 마이크로서비스를 빌드하는 방법을 설명했습니다.

코드는 Github에서 사용할 수 있습니다 . 이것은 Maven 기반 프로젝트이므로 그대로 가져오고 실행하는 것이 간단해야 합니다.

Cloud footer banner