Last weekend I wrote an article on creating the smallest possible Docker image for my JHipster application. The result was a 180Mb Docker image which starts on avg. in 56 seconds on Google Cloud.
在这个比利时雨天,我决定创建一个JHipster应用程序,该应用程序具有最快的启动速度。
Executive summary
59Mb占用空间和0.056s启动时间😱💪🏻
Quarkus to the rescue!
自从Red Hat宣布Quarkus以来,我就想参与这个新项目,今天就是那天。
我的目标基本上是替换现有的Spring Boot应用程序(由JHipster生成)并将其替换为Quarkus本机版本。 让我们看看我们能走多远。
我想模仿JHipster使用的包结构,这很合乎逻辑。 在服务包下,您还将找到DTO和Mappers。
Domain: Hibernate with Panache
让我们首先创建一个(会议)事件域对象,该对象具有名称和描述字段。
package com.devoxx.hipster.domain;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Cacheable
@Entity(name = "hipster_event")
public class Event extends PanacheEntity {
@NotNull@Size(min = 3, max = 100)
@Column(nullable = false)
public String name;
public String description;
}
Hibernate Panache使我想起了Lombok(不需要getter和setter),此外,您还可以使用bean验证。 使用简单的@Cacheable批注,可以激活Infinispan缓存。
EventRepository
package com.devoxx.hipster.repository;
import com.devoxx.hipster.domain.Event;
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class EventRepository implements PanacheRepositoryBase<Event,Integer> {
public Event findByName(String name){
return find("name", name).firstResult();
}
}
查找器方法在EventRepository中创建。 对于我的简单CRUD Web应用程序,当前仅是一个示例方法。 我希望在以后的版本中添加分页和排序功能。
基本域对象可以直接提供给Angular,但是在引入更复杂和机密的字段(例如电子邮件或OAuth机密)时,您希望在域和Web REST包之间有一个DTO映射器。
MapStruct
MapStruct是一个代码生成器,它基于约定优于配置的方法极大地简化了Java bean类型之间映射的实现。
JHipster heavily depends on MapStruct so I needed to investigate if it was possible with Quarkus. The actual Quarkus website doesn't mention it but Google did return some recent effort to make MapStruct part of the Quarkus eco-system, great!
package com.devoxx.hipster.service.mapper;
import com.devoxx.hipster.domain.Event;
import com.devoxx.hipster.service.dto.EventDTO;
import org.mapstruct.Mapper;
@Mapper(config = QuarkusMappingConfig.class)
public interface EventMapper {
EventDTO toDto(Event event);
Event toEntity(EventDTO eventDTO);
}
EventMapper需要引用QuarkusMappingConfig接口,该接口告诉Quarkus它使用CDI进行依赖项注入。 Spring DI支持Quarkus,但不确定MapStruct是否已支持它?
package com.devoxx.hipster.service.mapper;
import org.mapstruct.MapperConfig;
@MapperConfig(componentModel = "cdi")
interface QuarkusMappingConfig {
}
域模型,DTO和Mappers完成ONE
Service Layer
EventService非常轻巧,我很想将这种逻辑移到EventResource类中,但是在这些垂直层之间进行清晰的分隔最终将是一件好事。 所以我们开始...
@ApplicationScoped
public class EventService {
@Inject
EventMapper eventMapper;
/**
* Get all events.
*
* @return list of event DTOs.
*/public List<EventDTO> getAll() {
Stream<Event> events = Event.streamAll();
return events.map(event -> eventMapper.toDto(event) )
.collect(Collectors.toList());
}
}
最终服务还包括用于获取一个特定事件(按ID)并保存DTO的代码。
EventResource
@Path("api/events")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class EventResource {
@Inject
EventService eventService;
//...
@GETpublic List<EventDTO> getEvents() {
List<EventDTO> allEvents = eventService.getAll();
if (allEvents == null) {
throw new WebApplicationException("No events available", HttpURLConnection.HTTP_NOT_FOUND);
}
return allEvents;
}
//...
}
EventResource再次没有真正的惊喜。 Quarkus使用RestEasy,这是一个拨码开关,我需要从Spring REST改变脖子,但我会生存并学习。
Functional Testing
编写一些消耗REST端点的功能测试还是一种有趣的体验,看起来如下。
谈到乐趣,Quarkus还支持Kotlin。 接下来应该尝试一下。
@QuarkusTest
class EventEndpointTest {
@Test
void testGetOneEvent() {
given()
.when().get("/api/events/1")
.then()
.statusCode(HttpURLConnection.HTTP_OK)
.assertThat()
.body(containsString("Devoxx"),
containsString("for developers"));
}
@Test
void testGetAllEvents() {
//List all, the database has initially 2 events
given()
.when().get("/api/events")
.then()
.statusCode(HttpURLConnection.HTTP_OK)
.assertThat()
.body("size()", is(2));
}
}
Let's FlyWay
JHipster使用Liquibase,但Quarkus(目前)仅支持FlyWay(Axel感谢 邀请,但看着啤酒我已经很头疼🤪)。
除了添加FlyWay Maven依赖项,您还需要通过在application.properties文件中添加以下行来激活它。
# Flyway minimal config properties
quarkus.flyway.migrate-at-start=true
然后,在resources / db / migration目录中添加SQL语句。 不要忘记添加PostgreSQL序列生成器,否则新的域对象将不会获得任何ID。
CREATE SEQUENCE hibernate_sequence START 10;
CREATE TABLE hipster_event
(
id INT,
name VARCHAR(100),
description VARCHAR(255)
);
INSERT INTO hipster_event(id, name, description)
VALUES (1, 'Devoxx Belgium 2019', 'The developers conference from developers for developers'),
(2, 'Devoxx UK 2019', 'The developers conference in London');
现在我们已经有了所有逻辑,让我们看看如何运行这个婴儿。
GraalVM
您需要下载GraalVM 1.0 rc16和Apache Maven 3.5.3+。
Note: GraalVM v19.0 is not yet supported by Quarkus but looks like the Red Hat team is on it @ https://github.com/quarkusio/quarkus/issues/2412
在maven编译并打包了项目之后,您现在可以启动Quarkus应用程序:
$ mvn quarkus:dev
真正酷的是Quarkus支持项目的热重载。 每当HTTP请求命中应用程序时,它都会重新加载应用程序,因为它只需要几毫秒的时间。 最终无需设置ZeroTurnaround的JRebel而进行热重装是一个非常不错的奖励。
下面列出了最重要的输出...
INFO [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
INFO [io.qua.fly.FlywayProcessor] (build-6) Adding application migrations in path: file:/Users/stephan/java/projects/quarkushipster/backend/target/classes/db/migration/
INFO [io.qua.fly.FlywayProcessor] (build-6) Adding application migrations in path: file:/Users/stephan/java/projects/quarkushipster/backend/target/classes/db/migration
INFO [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 703ms
INFO [org.fly.cor.int.lic.VersionPrinter] (main) Flyway Community Edition 5.2.4 by Boxfuse
INFO [org.fly.cor.int.dat.DatabaseFactory] (main) Database: jdbc:postgresql:quarkus_hipster (PostgreSQL 10.5)
INFO [org.fly.cor.int.com.DbValidate] (main) Successfully validated 1 migration (execution time 00:00.013s)
INFO [org.fly.cor.int.sch.JdbcTableSchemaHistory] (main) Creating Schema History table: "public"."flyway_schema_history"
INFO [org.fly.cor.int.com.DbMigrate] (main) Current version of schema "public": << Empty Schema >>
INFO [org.fly.cor.int.com.DbMigrate] (main) Migrating schema "public" to version 1.0.0 - HIPSTER
INFO [org.fly.cor.int.com.DbMigrate] (main) Successfully applied 1 migration to schema "public" (execution time 00:00.050s)
INFO [io.quarkus] (main) Quarkus 0.15.0 started in 1.781s. Listening on: http://[::]:8080
INFO [io.quarkus] (main) Installed features: [agroal, cdi, flyway, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy, resteasy-jsonb]
OOOOOooooh Quarkus在1.781秒内启动了我的简单CRUD Java应用程序。
Quarkus 0.15.0始于1.781s。
而且它甚至还没有在纯模式下运行
Going Native
在构建本机包之前,您需要安装GraalVM本机映像工具。 将shell目录更改为GraalVM bin目录,然后键入以下内容:
$ gu install native-image
现在,您可以创建Java应用程序的本机映像,这将花费几条鸣叫和一杯咖啡(大约3分钟,具体取决于您的计算机)。
$ mvn package -Dnative
maven命令将在目标目录中创建一个{project} {version} -runner应用程序。 您可以通过在Shell中执行来启动应用程序。
本地应用程序最初仅在大约5,056秒处启动,但似乎我必须更新/ etc / hosts并将主机名添加到localhost。
127.0.0.1 localhost Stephans-MacBook-Pro.local
::1 localhost Stephans-MacBook-Pro.local
添加后,本机应用程序将在0,056s中启动,如下所示,并且该应用程序只有56Mb小😱
And now the FrontEnd
速度冲击后不得不先放松一下,但现在让我们使用JHipster生成Angular 7应用程序。
JHipster Client
我们只需要创建项目的Angular端,您可以执行以下操作:
$ jhipster --skip-server
创建之后,我们现在可以导入JDL(JHipster域语言)文件,该文件将创建所有相关的Angular CRUD页面和逻辑。 当前的JDL中只有事件域模型,其中包含两个字段:名称和描述。
$ jhipster import-jdl jhipster.jdl
这太容易了,前面的命令在不到5分钟的时间内生成了最新的Angular TypeScript代码。 一个普通的开发人员需要一天(或更长时间)才能完成此任务,并且可能需要一周向客户收费!
You run the Angular JHipster web app using npm start and then open your browser and point it to http://localhost:9000
这是您得到的:
为了显示会议事件数据,我必须在Quarkus中实现一些模拟REST端点,以用于用户身份验证和帐户详细信息。
我还禁用了event.route.ts文件中的ROLE_USER权限,因为尚未对其进行配置。
一旦做出了这些更改,我就可以享受我的CRUD逻辑了。...呃,等等...什么? 该死的...浏览器不喜欢访问端口9000和访问端口8080上的REST后端端点。跨域资源共享(CORS)。
嗯,Quarkus将如何处理CORS?
Google为我指出了一个我必须添加到Quarkus项目中的示例CorsFilter,而且成功了!
现在,我们有了一个由JHipster创建的(没有安全的)Angular Web应用程序,并与Quarkus后端进行了对话,其启动时间少于2秒,并且Web和Java模块都可以热重载。
What's next?
另一个阴雨的周末应该允许我添加RBAC和JWT(Quarkus支持),但与此同时,您可以从
Quarkus JHipster demo project
This is a basic JHipster Angular CRUD application using Quarkus as the backend service.
Checkout also my related LinkedIn article.
The backend code is very straight forward and uses the following Quarkus (extensions) :
- RESTEasy to expose the REST endpoints
- Hibernate ORM with Panache to perform the CRUD operations on the database
- MapStruct for DTO mapping
- FlyWay version control for the database tables
- ArC, the CDI inspired dependency injection tool with zero overhead
- The high performance Agroal connection pool
- Infinispan based caching
- All safely coordinated by the Narayana Transaction Manager
- A PostgreSQL database; see below to run one via Docker
This demo application is based on the Quarkus example project 'hibernate-orm-panache-resteasy' provided by the RedHat team @ https://github.com/quarkusio/quarkus-quickstarts
Thanks to the Quarkus (Red Hat), JHipster, GraaVM teams for their amazing work!
Requirements
To compile and run this demo you will need:
- GraalVM
1.0 rc16
- Apache…
Or on GitLab @ https://gitlab.com/voxxed/quarkushipster
我接受合并请求😎👍🏼
再次感谢GraalVM,Quarkus和JHipster团队使这一魔术成为可能!
Addendum
发表文章后,我在Twitter上收到了一些有趣的反馈,看起来启动时间仍然太慢了😎
Sanne@sannegrinovero
@Stephan007 @QuarkusIO @juliendubois @angular @GetMapStruct @FlywayDb @Infinispan @springboot hi @Stephan007 ! Many thanks for the great article, but the boot times you got are way too slow 😅 something is wrong, I'll check it out ... cc/ @emmanuelbernard @QuarkusIO10:07 AM - 20 May 2019
在纯模式下,我应该大约达到0.015s,但是由于某些未知原因(DNS解析?),我的本机应用仅在5秒钟后启动。 根据Emmanuel的说法,这可能与某些缓慢/不可用的DNS解析有关。
Emmanuel Bernard@emmanuelbernard
@Stephan007 @SanneGrinovero @shelajev @QuarkusIO @juliendubois @angular @GetMapStruct @FlywayDb @Infinispan @springboot I’ve seen something like that on crappy networks. We do suspect some slow/unavailable DNS resolution of sort. Seems to be a general Java problem though vs just Quarkus.13:31 PM - 20 May 2019
通过将我的主机名添加到/ etc / hosts中的localhost,解决了上述问题!
您可以在我的Twitter时间轴上关注相关讨论。
![]()
Stephan@stephan007
JHipster meets Quarkus, a demo project journey article with bleeding fast startup and small footprint! linkedin.com/pulse/jhipster…06:44 AM - 20 May 2019