Recentemente, comecei a trabalhar com o Ratpack e estou gostando bastante. Na maioria das vezes, fiz projetos rápidos do zero. Mas gostaria de usá-lo em um aplicativo Spring Boot existente para substituir a parte tradicional do MVC. Na verdade, isso é fácil de fazer, pois tudo já foi pensado graças ao Módulo de mola.
Se você acompanha este blog, talvez se lembre de uma postagem antiga sobre armazenamento, indexação e pesquisa arquivos com Couchbase e Spring Boot. Usarei o código relacionado como exemplo. A ideia é substituir o Spring MVC pelo Ratpack e tornar meus serviços legados, síncronos e bloqueadores, assíncronos e não bloqueadores. O código resultante está disponível em github também.
Adição das dependências corretas
Estou usando o Gradle. O Ratpack é muito bem integrado a ele. Tudo o que você precisa fazer para adicionar um módulo é adicionar a dependência correta chamando ratpack.dependency("myFavoriteModule"). Portanto, em nosso caso, para adicionar suporte ao Spring Boot, você precisa adicionar ratpack.dependency("spring-boot"). Infelizmente, a versão gerenciada automaticamente pelo Ratpack é inferior a 1.4.0.M3, que é a versão que traz a configuração automática do Couchbase. Portanto, desta vez terei que adicionar as dependências manualmente.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
dependências { compilar pacote de rato.dependência("guice"), pacote de rato.dependência("rx"), pacote de rato.dependência("guidão"), "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" } |
O que você pode ver aqui é que ratpack.dependency("spring-boot") é um atalho para adicionar org.springframework.boot:spring-boot-autoconfigure:1.4.0.M3 e io.ratpack:ratpack-spring-boot:1.3.3. O que este módulo oferece a você é a capacidade de integrar um servidor Ratpack ao seu aplicativo Spring. Você poderá recuperar o Spring @Beans do contexto do Ratpack e declarar manipuladores como configuração do Spring.
Declarar a configuração do Ratpack
Uma coisa que você deve adorar no Spring Boot é a configuração automática. Você só precisa ter certeza de que o SDK do Couchbase está no classpath e que a propriedade spring.couchbase.bootstrap-hosts é declarado. Nesse momento, os Spring beans serão instanciados para um Bucket padrão. E essa instância do bucket estará disponível como um bean ou no contexto do Ratpack. Portanto, você não precisa declarar nenhuma ligação para o Couchbase na camada do Ratpack.
A primeira coisa que você tradicionalmente faz com o Ratpack é iniciar um servidor e definir a configuração e os manipuladores. Aqui já temos um aplicativo Spring Boot em execução. Todas as classes anotadas com @Configuration serão coletadas automaticamente e adicionadas à configuração do aplicativo. A primeira etapa para declarar essa configuração é criar uma classe que implemente RatpackServerCustomizer e anotá-lo com @Confguration. Isso permite que você defina uma lista de manipuladores, associações e configuração do servidor. No exemplo a seguir, estou registrando algumas propriedades do servidor e vinculando várias classes ao contexto do Ratpack. A propriedade 'server.maxContentLength' é o tamanho máximo do arquivo que você pode carregar.
|
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
pacote org.couchbase.desenvolver; importação java.util.ArrayList; importação java.util.Coleções; importação java.util.Lista; importação java.util.Mapa; importação org.couchbase.desenvolver.domínio.Renderizador de arquivo armazenado; importação org.couchbase.desenvolver.serviço.Serviço de busca; importação org.estrutura de mola.contexto.anotação.Configuração; importação com.google.comum.coletar.ImmutableMap; importação pacote de rato.formulário.Formulário; importação pacote de rato.func.Ação; importação pacote de rato.guice.BindingsSpec; importação pacote de rato.guidão.Módulo de guidão (HandlebarsModule); importação pacote de rato.guidão.Modelo; importação pacote de rato.manuseio.Cadeia; importação pacote de rato.rx.RxRatpack; importação pacote de rato.servidor.BaseDir; importação pacote de rato.servidor.Construtor de configuração de servidor; importação pacote de rato.mola.configuração.RatpackServerCustomizer; importação rx.Observável; @Configuração público classe Configuração do Ratpack implementa RatpackServerCustomizer { @Substituir público Lista<ação> getHandlers() { Lista<ação> manipuladores = novo ArrayList<ação>(); manipuladores.adicionar(fileApi()); retorno manipuladores; } @Substituir público Ação getServerConfig() { retorno configuração -> configuração.baseDir(BaseDir.encontrar()) .adereços(ImmutableMap.de("server.maxContentLength", "100000000", "app.name", "Pesquisar arquivo da loja")); } @Substituir público Ação getBindings() { retorno bindingConfig -> bindingConfig.módulo(Módulo de guidão (HandlebarsModule).classe).vincular(Manipulador de arquivos.classe) .vincular(Renderizador de arquivo armazenado.classe).vincular(Modelo de manipulador de erro.classe).vincular(ClientHandlerImpl.classe); } privado Ação fileApi() { retorno cadeia -> cadeia.prefixo("arquivo", Manipulador de arquivos.classe).postagem("fulltext", ctx -> { ctx.analisar(Formulário.classe).então(formulário -> { Cordas queryString = formulário.obter("queryString"); Serviço de busca searchService = ctx.obter(Serviço de busca.classe); Observável<mapa<string, objeto="">> arquivos = searchService.searchFulltextFiles(queryString); RxRatpack.promessa(arquivos).então(resposta -> ctx .renderizar(Modelo.guidãoTemplate("uploadForm", "text/html", m -> m.colocar("arquivos", resposta)))); }); }).postagem("n1ql", ctx -> { ctx.analisar(Formulário.classe).então(formulário -> { Cordas queryString = formulário.obter("queryString"); Serviço de busca searchService = ctx.obter(Serviço de busca.classe); Observável<mapa<string, objeto="">> arquivos = searchService.searchN1QLFiles(queryString); RxRatpack.promessa(arquivos).então(resposta -> ctx .renderizar(Modelo.guidãoTemplate("uploadForm", "text/html", m -> m.colocar("arquivos", resposta)))); }); }); } } </mapa<string,></mapa<string,></ação</ação</ação |
O sistema de modelos de aplicativos depende do Handlebars, portanto, você precisa do HandlebarsModule. O FileHandler tratará de todas as chamadas para a API '/file' e o StoredFileRenderer garantirá que o StoredFile seja renderizado corretamente. As duas últimas associações são para gerenciamento de erros.
A coisa mais importante que está acontecendo aqui é a fileAPI que declara meu manipulador. Um manipulador define o que acontece quando um usuário acessa um determinado URL. Aqui associamos todas as chamadas '/file/*' à classe FileHandler. Também definimos o comportamento para POST em '/fulltext' e '/n1ql'.
O Ratpack usa promessas. Portanto, quando você analisa um formulário proveniente de uma solicitação POST, obtém uma promessa. O que você pode ver em cada um desses POST é que o SearchService é obtido do contexto do Ratpack. Mesmo que ele nunca tenha sido vinculado na configuração. Isso ocorre porque os Spring beans estão disponíveis no contexto como parte da integração.
A próxima etapa é chamar esse serviço de pesquisa que retorna um Observable. Podemos usar o módulo rx-java do Ratpack que fornece um wrapper para Observables. Ele envolverá isso como uma promessa. Em seguida, você pode simplesmente renderizar a resposta.
Nesse ponto, nos livramos de todos os arquivos Spring MVC controladores. Como você pode ver, meu serviço retorna um Observable. O que não é o caso em meu aplicativo anterior.
Migração de serviços para o Ratpack
A maioria dos meus serviços depende do Couchbase. O SDK é baseado no RxJava, portanto, é muito fácil converter a maioria deles em um modo assíncrono e sem bloqueio e fazer com que retornem Observable.
Usando o RxJava
Este é um exemplo muito simples. É uma consulta N1QL que mapeia os resultados para uma lista de mapas. As duas primeiras linhas não mudam em nada, pois estão definindo principalmente a consulta. Você pode ver que o mapeamento parece mais natural quando se usa o bucket síncrono na segunda versão.
|
1 2 3 4 5 6 7 8 9 10 |
público Lista<mapa<string, objeto="">> searchN1QLFiles(Cordas whereClause) { N1qlQuery consulta = N1qlQuery.simples( "SELECT binaryStoreLocation, binaryStoreDigest FROM `default` WHERE type= 'file'" + whereClause); consulta.parâmetros().consistência(Consistência de varredura.STATEMENT_PLUS); N1qlQueryResult res = balde.consulta(consulta); Lista<mapa<string, objeto="">> nomes de arquivos = res.todas as linhas().fluxo().mapa(fila -> fila.valor().toMap()) .coletar(Colecionadores.toList()); retorno nomes de arquivos; } </mapa<string,></mapa<string,> |
Torna-se
|
1 2 3 4 5 6 7 |
público Observável<mapa<string, objeto="">> searchN1QLFiles(Cordas whereClause) { N1qlQuery consulta = N1qlQuery.simples( "SELECT binaryStoreLocation, binaryStoreDigest FROM `default` WHERE type= 'file'" + whereClause); consulta.parâmetros().consistência(Consistência de varredura.STATEMENT_PLUS); retorno balde.assíncrono().consulta(consulta).flatMap(AsyncN1qlQueryResult::linhas).mapa(r -> r.valor().toMap()); } </mapa<string,> |
E quanto ao bloqueio de código legado?
Alguns dos meus serviços dependem de código antigo e bloqueador. Embora não exista uma maneira mágica de torná-los não bloqueantes, podemos facilmente envolvê-los em uma Promise. Isso nos permitirá usá-las facilmente nos manipuladores. Envolver a chamada de bloqueio é muito fácil, tudo o que você precisa fazer é envolver sua função com "Blocking.get()". Aqui está um exemplo muito simples:
|
1 2 3 4 |
público Cordas getSha1Digest(InputStream é) { retorno DigestUtils.sha1Hex(é); } |
torna-se
|
1 2 3 4 |
público Promessa getSha1Digest(InputStream é) { retorno Bloqueio.obter(() -> DigestUtils.sha1Hex(é)); } |
Conclusão
Agora você sabe praticamente tudo o que precisa saber para dar um pouco de Ratpack Love ao seu aplicativo Spring Boot. Se achar que está faltando alguma coisa, entre em contato comigo em twitter ou nos comentários abaixo.