Quarkusフレヌムワヌククリヌンなアヌキテクチャの実装方法

こんにちは、Habr



新しい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に投皿されおいたす。



All Articles