저는 최근에 Ratpack을 사용하기 시작했는데 꽤 마음에 듭니다. 주로 처음부터 시작하는 간단한 프로젝트들을 진행했습니다. 하지만 기존 스프링 부트(Spring Boot) 애플리케이션에서 전통적인 MVC 부분을 대체하기 위해 그것을 사용하고 싶습니다. 이것은 모든 것이 이미 잘 마련되어 있기 때문에 실제로 쉽게 할 수 있습니다. 스프링 모듈.
이 블로그를 구독하신다면 에 관한 예전 게시물을 기억하실지도 모릅니다 저장, 색인 및 검색 ~가 있는 파일 카우치베이스 그리고 스프링 부트를 사용할 것입니다. 관련 코드 예를 들어, 핵심 아이디어는 Spring MVC를 Ratpack으로 교체하고 기존의 레거시, 동기식, 블로킹 서비스를 비동기 및 넌블로킹(non-blocking) 방식으로 전환하는 것입니다. 결과 코드는 다음에서 확인할 수 있습니다. 깃허브 또한.
올바른 의존성 추가하기
나는 Gradle을 사용하고 있다. Ratpack은 이것과 매우 잘 통합되어 있다. 모듈을 추가하기 위해 해야 할 일은 올바른 의존성을 호출하여 추가하는 것뿐이다. ratpack.dependency("myFavoriteModule"). 따라서 저희의 경우, Spring Boot에 대한 지원을 추가하려면 추가해야 합니다 ratpack.dependency("spring-boot"). 안타깝게도 Ratpack이 자동으로 관리하는 버전은 자동 Couchbase 구성을 지원하는 버전인 1.4.0.M3보다 낮습니다. 따라서 이번에는 의존성을 수동으로 추가해야 합니다.
dependencies {
compile ratpack.dependency("guice"),
ratpack.dependency("rx"),
ratpack.dependency("handlebars"),
"com.couchbase.client:java-client:2.3.1",
"org.springframework.boot:spring-boot-autoconfigure:1.4.0.M3",
"io.ratpack:ratpack-spring-boot:1.3.3",
"org.slf4j:slf4j-simple:1.7.12",
"org.codehaus.plexus:plexus-utils:3.0.21",
"commons-codec:commons-codec:1.10"
}
여기서 볼 수 있는 것은 ratpack.dependency("spring-boot") 추가하는 바로 가기입니다 org.springframework.boot:spring-boot-autoconfigure:1.4.0.M3 그리고 io.ratpack:ratpack-spring-boot:1.3.3. 이 모듈을 사용하면 Ratpack 서버를 Spring 애플리케이션에 통합할 수 있습니다. Ratpack 컨텍스트에서 Spring @Bean을 가져오고, 핸들러를 Spring 구성으로 선언할 수 있습니다.
Ratpack 구성 선언
Spring Boot에서 정말 좋아해야 할 한 가지는 자동 설정입니다. Couchbase SDK가 클래스패스에 있고 속성이 있는지 확인하기만 하면 됩니다. spring.couchbase.bootstrap-hosts 가 선언됩니다. 그 순간 Spring 빈이 기본 버킷을 위해 인스턴스화됩니다. 그리고 이 버킷 인스턴스는 빈으로 또는 Ratpack의 컨텍스트에서 사용할 수 있게 됩니다. 따라서 Ratpack 레이어에 Couchbase용 바인딩을 선언할 필요가 없습니다.
Ratpack으로 전통적으로 가장 먼저 하는 일은 서버를 시작하고 설정과 핸들러를 정의하는 것입니다. 여기서는 이미 실행 중인 Spring Boot 애플리케이션이 있습니다. @Configuration이 주석으로 지정된 모든 클래스는 자동으로 감지되어 애플리케이션 설정에 추가됩니다. 해당 설정을 선언하는 첫 번째 단계는 구현하는 클래스를 만드는 것입니다 RatpackServerCustomizer 그리고 @Configuration으로 어노테이트하세요. 이를 통해 핸들러, 바인딩, 그리고 서버 설정 목록을 정의할 수 있습니다. 다음 예제에서는 몇 가지 서버 속성을 등록하고 여러 클래스를 Ratpack의 컨텍스트에 바인딩하고 있습니다. ‘server.maxContentLength’ 속성은 업로드할 수 있는 파일의 최대 크기입니다.
package org.couchbase.devex;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.couchbase.devex.domain.StoredFileRenderer;
import org.couchbase.devex.service.SearchService;
import org.springframework.context.annotation.Configuration;
import com.google.common.collect.ImmutableMap;
import ratpack.form.Form;
import ratpack.func.Action;
import ratpack.guice.BindingsSpec;
import ratpack.handlebars.HandlebarsModule;
import ratpack.handlebars.Template;
import ratpack.handling.Chain;
import ratpack.rx.RxRatpack;
import ratpack.server.BaseDir;
import ratpack.server.ServerConfigBuilder;
import ratpack.spring.config.RatpackServerCustomizer;
import rx.Observable;
@Configuration
public class RatpackConfiguration implements RatpackServerCustomizer {
@Override
public List getHandlers() {
List handlers = new ArrayList();
handlers.add(fileApi());
return handlers;
}
@Override
public Action getServerConfig() {
return config -> config.baseDir(BaseDir.find())
.props(ImmutableMap.of("server.maxContentLength", "100000000", "app.name", "Search Store File"));
}
@Override
public Action getBindings() {
return bindingConfig -> bindingConfig.module(HandlebarsModule.class).bind(FileHandler.class)
.bind(StoredFileRenderer.class).bind(ErrorHandlerImpl.class).bind(ClientHandlerImpl.class);
}
private Action fileApi() {
return chain -> chain.prefix("file", FileHandler.class).post("fulltext", ctx -> {
ctx.parse(Form.class).then(form -> {
String queryString = form.get("queryString");
SearchService searchService = ctx.get(SearchService.class);
Observable<map> files = searchService.searchFulltextFiles(queryString);
RxRatpack.promise(files).then(response -> ctx
.render(Template.handlebarsTemplate("uploadForm", "text/html", m -> m.put("files", response))));
});
}).post("n1ql", ctx -> {
ctx.parse(Form.class).then(form -> {
String queryString = form.get("queryString");
SearchService searchService = ctx.get(SearchService.class);
Observable<map> files = searchService.searchN1QLFiles(queryString);
RxRatpack.promise(files).then(response -> ctx
.render(Template.handlebarsTemplate("uploadForm", "text/html", m -> m.put("files", response))));
});
});
}
}
</map</map
애플리케이션 템플릿 시스템은 Handlebars를 기반으로 하므로 HandlebarsModule이 필요합니다. FileHandler는 ‘/file’ API에 대한 모든 호출을 처리하며, StoredFileRenderer는 StoredFile이 올바르게 렌더링되도록 보장합니다. 마지막 두 바인딩은 오류 처리를 위한 것입니다.
여기서 가장 중요한 일은 파일API 내 핸들러를 선언하는 메서드입니다. 핸들러는 사용자가 특정 URL을 호출했을 때 어떤 동작이 수행될지를 정의합니다. 여기서는 ‘/file/*’에 대한 모든 호출을 FileHandler 클래스와 연결합니다. 또한 ‘/fulltext’ 및 ‘/n1ql’에 대한 POST 요청의 동작도 정의합니다.
Ratpack은 Promise를 사용합니다. 따라서 POST 요청에서 Form을 파싱할 때 Promise를 얻게 됩니다. 각 POST에서 볼 수 있듯이 SearchService는 Ratpack의 컨텍스트에서 가져옵니다. 설정에서 바인딩된 적이 없더라도 말입니다. 이는 통합의 일환으로 Spring 빈을 컨텍스트에서 사용할 수 있기 때문입니다.
다음 단계는 Observable을 반환하는 해당 검색 서비스를 호출하는 것입니다. Observable용 래퍼를 제공하는 Ratpack의 rx-java 모듈을 사용할 수 있습니다. 그러면 이것이 promise로 래핑됩니다. 그런 다음 응답을 간단히 렌더링하면 됩니다.
이 시점에서 우리는 모든 Spring MVC를 제거했습니다 컨트롤러. 보시다시피 제 서비스는 Observable을 반환합니다. 이전 애플리케이션에서는 그렇지 않았습니다.
랫팩용 서비스 마이그레이션
대부분의 서비스가 카우치베이스(Couchbase)에 의존하고 있습니다. SDK가 RxJava 기반이어서 대부분 비동기 및 넌블로킹 방식으로 쉽게 전환하고 Observable을 반환하도록 만들 수 있습니다.
RxJava 사용하기
이것은 아주 간단한 예제입니다. 결과를 Map의 List로 매핑하는 N1QL 쿼리입니다. 처음 두 줄은 주로 쿼리를 정의하는 것이므로 전혀 변경되지 않습니다. 두 번째 버전에서는 동기식 버킷을 사용할 때 매핑이 더 자연스럽게 느껴지는 것을 볼 수 있습니다.
public List<map<string, object="">> searchN1QLFiles(String whereClause) {
N1qlQuery query = N1qlQuery.simple(
"SELECT binaryStoreLocation, binaryStoreDigest FROM `default` WHERE type= 'file' " + whereClause);
query.params().consistency(ScanConsistency.STATEMENT_PLUS);
N1qlQueryResult res = bucket.query(query);
List<map<string, object="">> filenames = res.allRows().stream().map(row -> row.value().toMap())
.collect(Collectors.toList());
return filenames;
}
</map<string,></map<string,>
된다
public Observable<map<string, object="">> searchN1QLFiles(String whereClause) {
N1qlQuery query = N1qlQuery.simple(
"SELECT binaryStoreLocation, binaryStoreDigest FROM `default` WHERE type= 'file' " + whereClause);
query.params().consistency(ScanConsistency.STATEMENT_PLUS);
return bucket.async().query(query).flatMap(AsyncN1qlQueryResult::rows).map(r -> r.value().toMap());
}
</map<string,>
레거시 코드의 차단을 어떻게 해야 할까요?
내 서비스 중 일부는 오래된 블로킹 코드에 의존하고 있습니다. 이를 넌블로킹으로 만드는 마법 같은 방법은 없지만, 간단히 Promise로 감싸는 것은 가능합니다. 이를 통해 핸들러에서 쉽게 사용할 수 있습니다. 블로킹 호출을 감싸는 것은 매우 쉬우며, 함수를 ‘Blocking.get()’으로 감싸기만 하면 됩니다. 여기 아주 간단한 예시가 있습니다:
public String getSha1Digest(InputStream is) {
return DigestUtils.sha1Hex(is);
}
된다
public Promise getSha1Digest(InputStream is) {
return Blocking.get(() -> DigestUtils.sha1Hex(is));
}
결론
이제 Spring Boot 애플리케이션에 Ratpack을 적용하는 데 필요한 거의 모든 것을 알게 되었습니다. 빠진 부분이 있다고 느껴지시면 저에게 연락해 주세요 트위터 또는 아래 댓글에 남겨주세요.

댓글 남기기
댓글을 달기 위해서는 로그인해야합니다.