æ°ããJavaãã¬ãŒã ã¯ãŒã¯ã®èª¿æ»ãšãSpring Bootããã¯ãžã®é¢å¿ãç¶ç¶ããäžã§ãJavaçšã®æ°ããQuarkusãã¬ãŒã ã¯ãŒã¯ãæ€èšããŠããŸãã詳现ãªèª¬æã¯ããã«ãããŸãã仿¥ã¯ãQuarkusã䜿çšããŠã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ã«æºæ ããããšãããã«äŸ¿å©ã§ãããã瀺ãç°¡åãªèšäºã®ç¿»èš³ãèªãããšãææ¡ããŸãã
Quarkusã¯ãé¿ããããªããã¬ãŒã ã¯ãŒã¯ã®ã¹ããŒã¿ã¹ãæ¥éã«ç²åŸããŠããŸãããããã£ãŠãç§ã¯ããäžåºŠããã調ã¹ãŠãçŽç²ãªã¢ãŒããã¯ãã£ã®ååã«æºæ ããããã«ãããã©ã®çšåºŠåŠçããããã確èªããããšã«ããŸããã
åºçºç¹ãšããŠãã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ã®ååã«åŸã£ãŠCRUD RESTã¢ããªã±ãŒã·ã§ã³ãäœæããããã«ã5ã€ã®æšæºã¢ãžã¥ãŒã«ãæã€åçŽãªMavenãããžã§ã¯ããæ¡çšããŸããã
domainïŒãã¡ã€ã³ãªããžã§ã¯ããšãããã®ãªããžã§ã¯ãã®ã²ãŒããŠã§ã€ã€ã³ã¿ãŒãã§ã€ã¹app-apiïŒå®éã®ã±ãŒã¹ã«å¯Ÿå¿ããã¢ããªã±ãŒã·ã§ã³ã€ã³ã¿ãŒãã§ã€ã¹app-implïŒãµããžã§ã¯ããšãªã¢ã«ãããããã®ã±ãŒã¹ã®å®è£ ãäŸåapp-apiããŠdomainãinfra-persistenceïŒãã¡ã€ã³ãããŒã¿ããŒã¹APIãšå¯Ÿè©±ã§ããããã«ããã²ãŒããŠã§ã€ãå®è£ ããŸããã«äŸåãdomainãŸããinfra-webïŒRESTã䜿çšããŠå€çãšå¯Ÿè©±ããããã®èæ ®ãããã±ãŒã¹ãéããŸããã«äŸåãapp-apiãŸãã
ããã«
main-partitionããããã€å¯èœãªã¢ããªã±ãŒã·ã§ã³ã¢ãŒãã£ãã¡ã¯ããšããŠæ©èœããã¢ãžã¥ãŒã«ãäœæããŸãã
Quarkusã§ã®äœæ¥ãèšç»ããå Žåãæåã®ã¹ãããã¯ããããžã§ã¯ãã®POMãã¡ã€ã«ã«BOM仿§ã远å ããããšã§ãããã®BOMã¯ã䜿çšããäŸåé¢ä¿ã®ãã¹ãŠã®ããŒãžã§ã³ã管çããŸãããŸããsurefireãã©ã°ã€ã³ãªã©ããã©ã°ã€ã³ç®¡çããŒã«ã§mavenãããžã§ã¯ãã®æšæºãã©ã°ã€ã³ãæ§æããå¿ èŠããããŸãã Quarkusã䜿çšããå Žåã¯ãããã§åãååã®ãã©ã°ã€ã³ãæ§æããŸããæåŸã«ãªããŸããããããã§ã¯ãåã¢ãžã¥ãŒã«ïŒ<build> <plugins> ... </ plugins> </ build>å ïŒã§åäœããããã«ãã©ã°ã€ã³ãã€ãŸãJandexãã©ã°ã€ã³ãæ§æããå¿ èŠããããŸãã..ã Quarkusã¯CDIã䜿çšãããããJandexãã©ã°ã€ã³ã¯åã¢ãžã¥ãŒã«ã«ã€ã³ããã¯ã¹ãã¡ã€ã«ã远å ããŸãããã®ãã¡ã€ã«ã«ã¯ããã®ã¢ãžã¥ãŒã«ã§äœ¿çšãããŠãããã¹ãŠã®æ³šéã®ã¬ã³ãŒããšãã©ã®æ³šéã䜿çšãããŠãããã瀺ããªã³ã¯ãå«ãŸããŠããŸãããã®çµæãCDIã®åŠçãã¯ããã«ç°¡åã«ãªããåŸã§å®è¡ããäœæ¥ãã¯ããã«å°ãªããªããŸãã
åºæ¬æ§é ã®æºåãã§ããã®ã§ãå®å šãªã¢ããªã±ãŒã·ã§ã³ã®æ§ç¯ãéå§ã§ããŸãããããè¡ãã«ã¯ãã¡ã€ã³ããŒãã£ã·ã§ã³ãQuarkuså®è¡å¯èœã¢ããªã±ãŒã·ã§ã³ãäœæããããšã確èªããå¿ èŠããããŸãããã®ã¡ã«ããºã ã¯ãQuarkusã§æäŸãããŠãããã¯ã€ãã¯ã¹ã¿ãŒããã®äŸã«ç€ºãããŠããŸãã
ãŸããQuarkusãã©ã°ã€ã³ã䜿çšããããã«ãã«ããæ§æããŸãã
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
次ã«ãåã¢ããªã±ãŒã·ã§ã³ã¢ãžã¥ãŒã«ã«äŸåé¢ä¿ã远å ããŸããããããã§ãäŸåé¢ä¿
quarkus-resteasyãšãäžç·ã«ãªãquarkus-jdbc-mysqlãŸããæåŸã®äŸåé¢ä¿ã§ã¯ãããŒã¿ããŒã¹ãæãé©åãªãã®ã«çœ®ãæããããšãã§ããŸãïŒåŸã§ãã€ãã£ãéçºãã¹ã«åŸããããH2ãªã©ã®çµã¿èŸŒã¿ããŒã¿ããŒã¹ã䜿çšã§ããªãããšãèæ
®ããŠãã ããïŒã
ãŸãã¯ããããã¡ã€ã«ã远å ããŠãåŸã§ãã€ãã£ãã¢ããªã±ãŒã·ã§ã³ããã«ãã§ããããã«ããããšãã§ããŸãããããè¡ãã«ã¯ã远å ã®éçºã¹ã¿ã³ãïŒGraalVMã
native-imageããã³OSXã䜿çšããŠããå Žåã¯XCode ïŒãæ¬åœã«å¿
èŠã§ãã
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
ããã§
mvn package quarkus:devããããžã§ã¯ãã«ãŒãããå®è¡ãããšãQuarkusã¢ããªãæ©èœããŸããã³ã³ãããŒã©ãŒãã³ã³ãã³ãããŸã ãªãã®ã§ããŸã èŠããã®ã¯ããŸããããŸããã
RESTã³ã³ãããŒã©ãŒã®è¿œå
ãã®æŒç¿ã§ã¯ãåšèŸºããã³ã¢ã«ç§»åããŸãããããŸãããŠãŒã¶ãŒããŒã¿ãè¿ãRESTã³ã³ãããŒã©ãŒãäœæããŸãããïŒãã®äŸã§ã¯ãããã¯åãªãååã§ãïŒã
JAX-RS APIã䜿çšããã«ã¯ãäŸåé¢ä¿ãã€ã³ãã©WebPOMã«è¿œå ããå¿ èŠããããŸãã
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
æãåçŽãªã³ã³ãããŒã©ãŒã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
@Path("/customer")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerResource {
@GET
public List<JsonCustomer> list() {
return getCustomers.getCustomer().stream()
.map(response -> new JsonCustomer(response.getName()))
.collect(Collectors.toList());
}
public static class JsonCustomer {
private String name;
public JsonCustomer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
ããã§ã¢ããªã±ãŒã·ã§ã³ãå®è¡ãããšãlocalhostïŒ8080 / customerãåŒã³åºããŠ
JoeãJSON圢åŒã§è¡šç€ºã§ããŸãã
ç¹å®ã®ã±ãŒã¹ã远å ãã
次ã«ããã®å®çšçãªã±ãŒã¹ã®ã±ãŒã¹ãšå®è£ ã远å ããŸãããã
app-api次ã®å Žåãå®çŸ©ããŸãããã
public interface GetCustomers {
List<Response> getCustomers();
class Response {
private String name;
public Response(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}
app-implãã®ã€ã³ã¿ãã§ãŒã¹ã®ç°¡åãªå®è£
ãäœæããŸãã
@UseCase
public class GetCustomersImpl implements GetCustomers {
private CustomerGateway customerGateway;
public GetCustomersImpl(CustomerGateway customerGateway) {
this.customerGateway = customerGateway;
}
@Override
public List<Response> getCustomers() {
return Arrays.asList(new Response("Jim"));
}
}
CDIãã³ã³ããŒãã³ãã衚瀺ããã«ã¯ã以äžã«å®çŸ©ãããŠ
GetCustomersImplããã«ã¹ã¿ã 泚éãå¿
èŠUseCaseã§ããæšæºã®ApplicationScopedãšannotationã䜿çšããããšãã§ãTransactionalãŸãããç¬èªã®æ³šéãäœæããããšã§ãã³ãŒããããè«ççã«ã°ã«ãŒãåããCDIãªã©ã®ãã¬ãŒã ã¯ãŒã¯ããå®è£
ã³ãŒããåãé¢ãããšãã§ããŸãã
@ApplicationScoped
@Transactional
@Stereotype
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
}
CDIã¢ãããŒã·ã§ã³ã䜿çš
app-implããã«ã¯ãapp-apiããã³äŸåé¢ä¿ã«å ããŠã次ã®äŸåé¢ä¿ãPOMãã¡ã€ã«ã«è¿œå ããå¿
èŠãããdomainãŸãã
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
次ã«ãRESTã³ã³ãããŒã©ãŒã倿ŽããŠã±ãŒã¹ã§äœ¿çšããå¿ èŠããã
app-apiãŸãã
...
private GetCustomers getCustomers;
public CustomerResource(GetCustomers getCustomers) {
this.getCustomers = getCustomers;
}
@GET
public List<JsonCustomer> list() {
return getCustomers.getCustomer().stream()
.map(response -> new JsonCustomer(response.getName()))
.collect(Collectors.toList());
}
...
ããã§ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããlocalhostïŒ8080 / customerãåŒã³åºããš
JimãJSON圢åŒã§è¡šç€ºãããŸãã
ãã¡ã€ã³ã®å®çŸ©ãšå®è£
次ã«ããã¡ã€ã³ã«çŠç¹ãåœãŠãŸããããã§ã®æ¬è³ªã¯
domainéåžžã«åçŽã§ãCustomeræ¶è²»è
ãåãå
¥ããã²ãŒããŠã§ã€ã€ã³ã¿ãŒãã§ã€ã¹ã§æ§æãããŠããŸãã
public class Customer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public interface CustomerGateway {
List<Customer> getAllCustomers();
}
ãŸããã²ãŒããŠã§ã€ã®äœ¿çšãéå§ããåã«ãã²ãŒããŠã§ã€ã®å®è£ ãæäŸããå¿ èŠããããŸãããã®ãããªã€ã³ã¿ãŒãã§ã€ã¹ã¯ã§æäŸãã
infra-persistenceãŸãã
ãã®å®è£ ã§ã¯ãQuarkusã§å©çšå¯èœãªJPAãµããŒãã䜿çšããPanacheãã¬ãŒã ã¯ãŒã¯ã䜿çšããŸããããã«ãããäœæ¥ãå°ã楜ã«ãªããŸãããã¡ã€ã³ã«å ããŠã
infra-persistence次ã®äŸåé¢ä¿ã远å ããå¿
èŠããããŸãã
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
ãŸããæ¶è²»è ã«å¯Ÿå¿ããJPAãšã³ãã£ãã£ãå®çŸ©ããŸãã
@Entity
public class CustomerJpa {
@Id
@GeneratedValue
private Long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Panacheã䜿çšããå Žåããšã³ãã£ãã£ãPanacheEntityãç¶æ¿ãããããªããžããª/ DAOãã¿ãŒã³ã䜿çšãããã®2ã€ã®ãªãã·ã§ã³ã®ãããããéžæã§ããŸããç§ã¯ActiveRecordãã¿ãŒã³ã®ãã¡ã³ã§ã¯ãªãã®ã§ãç§èªèº«ã¯ãªããžããªã«ç«ã¡å¯ããŸãããã©ã®ããã«äœæ¥ãããã¯ããªã次第ã§ãã
@ApplicationScoped
public class CustomerRepository implements PanacheRepository<CustomerJpa> {
}
JPAãšã³ãã£ãã£ãšãªããžããªãã§ããã®ã§ãã²ãŒããŠã§ã€ãå®è£ ã§ããŸã
Customerã
@ApplicationScoped
public class CustomerGatewayImpl implements CustomerGateway {
private CustomerRepository customerRepository;
@Inject
public CustomerGatewayImpl(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@Override
public List<Customer> getAllCustomers() {
return customerRepository.findAll().stream()
.map(c -> new Customer(c.getName()))
.collect(Collectors.toList());
}
}
ããã§ãã²ãŒããŠã§ã€ã䜿çšããããã«ãã±ãŒã¹ã®å®è£ ã§ã³ãŒãã倿Žã§ããŸãã
...
private CustomerGateway customerGateway;
@Inject
public GetCustomersImpl(CustomerGateway customerGateway) {
this.customerGateway = customerGateway;
}
@Override
public List<Response> getCustomer() {
return customerGateway.getAllCustomers().stream()
.map(customer -> new GetCustomers.Response(customer.getName()))
.collect(Collectors.toList());
}
...
Quarkusã¢ããªã±ãŒã·ã§ã³ã¯ãå¿ èŠãªæ°žç¶æ§ãã©ã¡ãŒã¿ãŒã䜿çšããŠæ§æããå¿ èŠãããããããŸã ã¢ããªã±ãŒã·ã§ã³ãéå§ã§ããŸãããã§ã¯
src/main/resources/application.propertiesã¢ãžã¥ãŒã«ãmain-partition次ã®ãã©ã¡ãŒã¿ã远å ããŸãã
quarkus.datasource.url=jdbc:mysql://localhost/test
quarkus.datasource.driver=com.mysql.cj.jdbc.Driver
quarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQL8Dialect
quarkus.datasource.username=root
quarkus.datasource.password=root
quarkus.datasource.max-size=8
quarkus.datasource.min-size=2
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=import.sql
å ã®ããŒã¿ã衚瀺ããããã«
import.sqlãããŒã¿ã远å ãããã®ãšåããã£ã¬ã¯ããªã«ãã¡ã€ã«ã远å ããŸãã
insert into CustomerJpa(id, name) values(1, 'Joe');
insert into CustomerJpa(id, name) values(2, 'Jim');
ããã§ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããlocalhostïŒ8080 / customerãåŒã³åºã
JoeãšJimãJSON圢åŒã§ã衚瀺ãããŸããã€ãŸããRESTããããŒã¿ããŒã¹ãŸã§ã®å®å
šãªã¢ããªã±ãŒã·ã§ã³ããããŸãã
ãã€ãã£ããªãã·ã§ã³
ãã€ãã£ãã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããå Žåã¯ãã³ãã³ãã䜿çšããŠãããè¡ãå¿ èŠããããŸã
mvn package -Pnativeãéçºã¹ã¿ã³ãã®å
容ã«ããããŸãããããã«ã¯æ°åãããå ŽåããããŸããQuarkusã¯èµ·åæã«éåžžã«é«éã§ããã€ãã£ããµããŒããªãã§ã2ã3ç§ã§èµ·åããŸãããGraalVMã䜿çšããŠãã€ãã£ãå®è¡å¯èœãã¡ã€ã«ã«ã³ã³ãã€ã«ãããšã察å¿ããæéã¯100ããªç§æªæºã«ççž®ãããŸããJavaã¢ããªã±ãŒã·ã§ã³ã®å Žåãããã¯éåžžã«é«éã§ãã
ãã¹ã
察å¿ããQuarkusãã¹ããã¬ãŒã ã¯ãŒã¯ã䜿çšããŠãQuarkusã¢ããªã±ãŒã·ã§ã³ããã¹ãã§ããŸãããã¹ã
@QuarkusTestã«æ³šéãä»ãããšãJUnitã¯æåã«Quarkusã³ã³ããã¹ããèµ·åããŠããããã¹ããå®è¡ããŸããã®ã¢ããªã±ãŒã·ã§ã³å
šäœã®ãã¹ãã¯main-partition次ã®ããã«ãªããŸãã
@QuarkusTest
public class CustomerResourceTest {
@Test
public void testList() {
given()
.when().get("/customer")
.then()
.statusCode(200)
.body("$.size()", is(2),
"name", containsInAnyOrder("Joe", "Jim"));
}
}
çµè«
å€ãã®ç¹ã§ãQuarkusã¯SpringBootã®æ¿ããç«¶äºçžæã§ããç§ã®æèŠã§ã¯ãQuarkusã®ããã€ãã®äºæã¯ããã«ãã解決ãããŠããŸãã app-implã«ã¯ãã¬ãŒã ã¯ãŒã¯ã®äŸåé¢ä¿ããããŸãããããã¯æ³šéã®äŸåé¢ä¿ã«ãããŸããïŒSpringã®å Žåã
spring-contextget@Componentã«è¿œå ãããšãSpringã³ã¢ã®äŸåé¢ä¿ã倿°è¿œå ãããŸãïŒããããæ°ã«å
¥ããªãå Žåã¯@ProducesãCDIã®æ³šéã䜿çšããŠã¡ã€ã³ã»ã¯ã·ã§ã³ã«Javaãã¡ã€ã«ã远å ããããã§ã³ã³ããŒãã³ããäœæããããšãã§ããŸãããã®å Žåãã«è¿œå ã®äŸåé¢ä¿ã¯å¿
èŠãããŸããapp-implãããããã©ãããããããjakarta.enterprise.cdi-apiç§ã¯ããã«äžæ¯ãããäžæ¯ãèŠãããšæã£ãŠããŸãspring-contextã
Quarkusã¯éããæ¬åœã«éãããã®ã¿ã€ãã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãSpringBootãããé«éã§ãã Clean Architectureã«ãããšããã¬ãŒã ã¯ãŒã¯ã®äŸåé¢ä¿ã®ã»ãšãã©ïŒãã¹ãŠã§ã¯ãªãã«ããŠãïŒã¯ã¢ããªã±ãŒã·ã§ã³ã®å€éšã«ååšããå¿ èŠããããããQuarkusãšSpringBootã®ã©ã¡ããéžæããããæããã«ãªããŸãããã®ç¹ã§ãQuarkusã®å©ç¹ã¯ãGraalVMã®ãµããŒããèæ ®ããŠããã«äœæããããããæå°éã®åŽåã§ã¢ããªã±ãŒã·ã§ã³ããã€ãã£ãã«å€æã§ããããšã§ãã Spring Bootã¯ããã®ç¹ã§ãŸã Quarkusã«é ãããšã£ãŠããŸãããããã«è¿œãã€ãããšã¯ééããããŸããã
確ãã«ãQuarkusã詊ããŠã¿ããšãå€å žçãªJakartaEEã¢ããªã±ãŒã·ã§ã³ãµãŒããŒã§Quarkusã䜿ãããšãã人ã ãåŸ ã£ãŠããå€ãã®äžå¹žã«æ°ä»ãããšãã§ããŸãããQuarkusã§ã§ããããšã¯ãŸã å€ããããŸãããããã®ã³ãŒããžã§ãã¬ãŒã¿ãŒã¯ãåŸæ¥ã®ã¢ããªã±ãŒã·ã§ã³ãµãŒããŒã䜿çšããJakartaEEã®ã³ã³ããã¹ãã§ã¯ãŸã ç°¡åã«äœ¿çšã§ããªãããŸããŸãªãã¯ãããžãŒããµããŒãããŠããŸããQuarkusã¯ãJakarta EEã«ç²ŸéããŠãã人ã ãå¿ èŠãšãããã¹ãŠã®åºæ¬ãã«ããŒããŠããããã®éçºã¯ã¯ããã«ã¹ã ãŒãºã§ããJavaãšã³ã·ã¹ãã ããã®çš®ã®ç«¶äºãã©ã®ããã«åŠçã§ããããèŠãã®ã¯è峿·±ãã§ãããã
ãã®ãããžã§ã¯ãã®ãã¹ãŠã®ã³ãŒãã¯Githubã«æçš¿ãããŠããŸãã