Kotlinを使用したJakartaEEアプリケーション
1. 概要
JavaとKotlinはどちらも、JVM用に設計された言語です。 ただし、Jakarta EEコンテナでKotlinを使用しようとすると、すぐにいくつかの慣用的な課題に遭遇します。
このチュートリアルでは、これらの課題とそれらに効果的に対処する方法を見ていきます。
2. 課題
JavaとKotlinは多少異なる言語です。 構文は異なりますが、それは明らかですが、それは実際の問題ではありません。言語設計とパラダイムが異なり、JakartaEEコンテナー内での使用にいくつかの問題が発生します。 Kotlinを使用してエンタープライズアプリケーションを構築するには、これらの違いに対応する必要があります。
たとえば、 Kotlinクラスはデフォルトでfinalです。拡張可能にするには、明示的に開く必要があります。 また、JPAやJacksonなどのフレームワークで使用するためのパラメーターなしのコンストラクターを提供する必要があります。 Javaではデフォルトで使用できますが、Kotlinでは追加の作業を行う必要があります。 Arquillian との統合テストと同様に、コンテナーが頻繁に使用するインジェクションも、初期化のために少し注意が必要です。
これらの課題に取り組みましょう。 この例では、 Kotlinで単純なCRUDアプリケーションを構築し、それをJakartaEEコンテナで実行する方法を示します。 単純なデータクラスから始めて、サービスと統合テストの作成に進みます。
3. 依存関係
したがって、まず最初に、javaee-api依存関係を追加しましょう。
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0.1</version>
<scope>provided</scope>
</dependency>
コンパイルにのみ必要なため、この依存関係を提供として設定していることに注意してください。
4. JPAエンティティ
それでは、entityとDTOとして同時に使用する単純なStudentデータクラスを作成しましょう。
学生のID、姓名が必要です。
@Entity
data class Student constructor (
@SequenceGenerator(name = "student_id_seq", sequenceName = "student_id_seq",
allocationSize = 1)
@GeneratedValue(generator = "student_id_seq", strategy = GenerationType.SEQUENCE)
@Id
var id: Long?,
var firstName: String,
var lastName: String
) {
constructor() : this(null, "", "")
constructor(firstName: String, lastName: String) : this(null, firstName, lastName)
}
Student をデータクラスとして定義しました。これは、データを保持するためのKotlinの特別な種類のクラスです。 これらのクラスでは、コンパイラは、プライマリコンストラクタで宣言されたすべてのプロパティから equals()、 hashCode()、 toString()などの一般的な関数を自動的に派生させます。
関数の自動生成により、データクラスは非常に便利で使いやすくなります。ただし、データクラスもいくつかのルールを満たす必要があります。 たとえば、valまたはvarとマークされたパラメーターが少なくとも1つあるプライマリコンストラクターが必要です。
データクラスでは、プライマリコンストラクター内のすべてのメンバーを定義しました。
また、2つのセカンダリコンストラクターを定義しました。
- まず、クラスをインスタンス化し、データにセッターを設定するために、コンテナーおよびJPAやJacksonなどの一般的なフレームワークが必要とするパラメーターなしのコンストラクターがあります。
- 次に、IDなしで新しいオブジェクトをインスタンス化するときに使用する便利なコンストラクターがあります。これは通常、新しいエンティティをデータベースに保存するときに使用します。
さらに、標準のJakarta EEの場合と同様に、標準のJPAアノテーションを使用してJPAエンティティを定義します。
3. ビジネスサービス
EntityManagerでCRUD操作を処理するビジネスサービスが必要です。 これは単純でJavaの実装に非常に似ていますが、いくつかの顕著な違いがあります。
@Stateless
open class StudentService {
@PersistenceContext
private lateinit var entityManager: EntityManager
open fun create(student: Student) = entityManager.persist(student)
open fun read(id: Long): Student? = entityManager.find(Student::class.java, id)
open fun update(student: Student) = entityManager.merge(student)
open fun delete(id: Long) = entityManager.remove(read(id))
}
Kotlinのすべてのクラスはfinalであるため、拡張機能を有効にするには、クラスを明示的に開く必要があります。 Jakarta EEコンテナは、サービスクラスからプロキシを作成し、必要な場所に挿入するため、拡張機能が必要です。 パブリックメソッドも開いている必要があります。開いていない場合、プロキシを作成できません。 したがって、クラスとそのすべてのパブリックメソッドでopenキーワードを使用します。
EntityManager は、Javaの場合と同様に、@PersistenceContextアノテーションを付けて使用されます。 追加のlateinitキーワードを持つプライベートメンバーとして定義します。 このキーワードは、この変数が最初は null であるが、最初に使用する前に初期化されることをコンパイラーに通知します。 これにより、不要な null チェックが不要になり、コンテナーインジェクションと完全に一致します。 @Injectアノテーションを使用するたびに使用します。
4. RESTサービス
最後に、アプリケーションのRESTサービスエンドポイントを定義する必要があります。 そのためには、リソースクラスを登録する必要があります。
@ApplicationPath("/")
class ApplicationConfig : Application() {
override fun getClasses() = setOf(StudentResource::class.java)
}
次に、リソースクラスを定義します。 Kotlin固有の変更をいくつか加えるだけで、Javaで行う作業に非常に近くなります。
@Path("/student")
open class StudentResource {
@Inject
private lateinit var service: StudentService
@POST
open fun create(student: Student): Response {
service.create(student)
return Response.ok().build()
}
@GET
@Path("/{id}")
open fun read(@PathParam("id") id: Long): Response {
val student = service.read(id)
return Response.ok(student, MediaType.APPLICATION_JSON_TYPE).build()
}
@PUT
@Path("/{id}")
open fun update(@PathParam("id") id: Long, student: Student): Response {
service.update(student)
return Response.ok(student, MediaType.APPLICATION_JSON_TYPE).build()
}
@DELETE
@Path("/{id}")
open fun delete(@PathParam("id") id: Long): Response {
service.delete(id)
return Response.noContent().build()
}
}
前の例のビジネスサービスのように、リソースクラスとすべてのパブリックメソッドを明示的に開きました。 また、 lateinit キーワードを再度使用しました。今回は、ビジネスサービスをリソースクラスに挿入するために使用しました。
5. Arquillianでのテスト
次に、アプリケーションの統合テストをArquillianおよびShrinkwrapと実装します。
Arquillianを使用してアプリケーションをテストするには、ShrinkWrapを使用してパッケージ化とテストコンテナーへのデプロイをセットアップする必要があります。
@RunWith(Arquillian.class)
public class StudentResourceIntegrationTest {
@Deployment
public static WebArchive createDeployment() {
JavaArchive[] kotlinRuntime = Maven.configureResolver()
.workOffline()
.withMavenCentralRepo(true)
.withClassPathResolution(true)
.loadPomFromFile("pom.xml")
.resolve("org.jetbrains.kotlin:kotlin-stdlib")
.withTransitivity()
.as(JavaArchive.class);
return ShrinkWrap.create(WebArchive.class, "kotlin.war")
.addPackages(true, Filters.exclude(".*Test*"), "com.baeldung.jeekotlin")
.addAsLibraries(kotlinRuntime)
.addAsResource("META-INF/persistence.xml")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}
// more code
}
デプロイメントは、統合テストの一般的なJavaデプロイメントに似ていますが、いくつかの追加構成が含まれています。 ここでは、Shrinkwrap Maven Resolverを使用して、Mavenリポジトリからkotlin-stdlibを取得します。 次に、それをライブラリとしてWARにアーカイブします。
その後、 HttpClient を使用して、RESTAPIに対してCRUDリクエストを実行します。
@Test
@RunAsClient
public void when_post__then_return_ok(@ArquillianResource URL url)
throws URISyntaxException, JsonProcessingException {
String student = new ObjectMapper()
.writeValueAsString(new Student("firstName", "lastName"));
WebTarget webTarget = ClientBuilder.newClient().target(url.toURI());
Response response = webTarget
.path("/student")
.request(MediaType.APPLICATION_JSON)
.post(Entity.json(student));
assertEquals(200, response.getStatus());
}
この例では、 @ArquillianResource を使用してAPIのURLを提供し、Studentオブジェクトをシリアル化してAPIにPOSTします。 すべてが正常で、オブジェクトがデータベースに作成されている場合、応答ステータスは200 OKです。これは、テストの最後にアサートされます。
6. 結論
この記事では、KotlinでCRUD REST JPAアプリケーションを構築する方法、それをデプロイする方法、Jakarta EEコンテナーで実行する方法、およびArquillianでテストする方法を示しました。 ご覧のとおり、KotlinとJavaは連携して機能しますが、それを実現するには追加の作業を行う必要があります。
この例の完全なソースコードは、GitHubのから入手できます。