Aplicativo Java EE com Kotlin

Aplicativo Java EE com Kotlin

1. Visão geral

Java eKotlin são linguagens projetadas parafor the JVM. No entanto, se tentarmos usar Kotlin no contêiner Java EE, encontraremos imediatamente alguns desafios idiomáticos.

Neste tutorial, veremos esses desafios e como enfrentá-los de forma eficaz.

2. Desafios

Java e Kotlin são linguagens um pouco diferentes. A sintaxe é diferente, isso é óbvio, mas esse não é o problema real. It’s language design and paradigm differ, e isso cria alguns problemas para uso em contêineres Java EE. Para criar aplicativos corporativos com o Kotlin, precisamos atender a essas diferenças.

Por exemplo,Kotlin classes are final by default. Para torná-los extensíveis, precisamos abri-los explicitamente. Também precisamos fornecer construtores sem parâmetros para uso com estruturas comoJPA ouJackson. Em Java, eles estão disponíveis por padrão, mas em Kotlin precisamos fazer algum trabalho extra. A injeção que os contêineres usam fortemente também será um pouco complicada por causa da inicialização, assim como o teste de integração comArquillian.

Vamos enfrentar esses desafios. Nosso exemplo demonstraráhow to build a simple CRUD application in Kotlin and run it in a Java EE container. Começaremos com uma classe de dados simples e depois seguiremos para escrever serviços e testes de integração.

3. Dependências

Então, com as primeiras coisas primeiro, vamos adicionar a dependênciajavaee-api:


    javax
    javaee-api
    8.0.1
    provided

Observe que estamos definindo essa dependência comoprovided, pois só precisamos dela para a compilação.

4. Entidade JPA

Agora, vamos escrever uma classe de dadosStudent simples que usaremos comoentityeDTO ao mesmo tempo.

Precisaremos da identificação, nome e sobrenome do aluno:

@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)
}

DefinimosStudent como uma classe de dados, que é um tipo especial de classe em Kotlin para armazenar dados. Nessas classes, o compilador deriva automaticamente funções comuns, comoequals(),hashCode() etoString() de todas as propriedades declaradas no construtor primário.

Automatic generation of functions makes data classes very convenient and easy to use. No entanto, as classes de dados também devem cumprir algumas regras. Por exemplo, eles devem ter um construtor primário com pelo menos um parâmetro marcado comoval ouvar.

Em nossa classe de dados, definimos todos os membros dentro do construtor principal.

Também definimos dois construtores secundários:

  • Primeiro, temos um construtor sem parâmetros que o contêiner e as estruturas comuns como JPA ou Jackson exigem para instanciar a classe e preencher os dados com setters.

  • Segundo, temos um construtor de conveniência que usamos quando queremos instanciar um novo objeto sem um ID, que normalmente usamos quando queremos salvar uma nova entidade no banco de dados.

Além disso, usamos anotações JPA padrão para definir a entidade JPA, assim como fazemos no Java EE padrão.

3. Serviço comercial

Precisamos de um serviço de negócios para lidar com as operações CRUD com umEntityManager. É simples e muito semelhante à implementação Java, com algumas diferenças notáveis:

@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))
}

Como todas as classes no Kotlin são finais, precisamos abrir explicitamente nossa classe para ativar a extensão. O contêiner Java EE precisa da extensão porque criará um proxy de nossa classe de serviço e o injetará onde for necessário. Os métodos públicos também precisam ser abertos, caso contrário, o proxy não poderá ser criado. Portanto,we use the open keyword on the class and all its public methods.

EntityManager é usado de maneira semelhante a Java com a anotação@PersistenceContext. Nós o definimos como um membro privado com umlateinit keyword adicional. Esta palavra-chave diz ao compilador que esta variável énull no início, mas que será inicializada antes do primeiro uso. Isso elimina verificações desnecessárias denull e se alinha perfeitamente com a injeção do recipiente. Usaremos sempre que usarmos a anotação@Inject.

4. Serviço REST

Finalmente, precisamos definir um terminal de serviço REST para nosso aplicativo. Para fazer isso, precisamos registrar nossa classe de recurso:

@ApplicationPath("/")
class ApplicationConfig : Application() {
    override fun getClasses() = setOf(StudentResource::class.java)
}

Agora, definimos nossa classe de recursos. Será muito parecido com o que faríamos em Java, com algumas modificações específicas do Kotlin:

@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()
    }

}

Abrimos explicitamente nossa classe de recursos e todos os métodos públicos, como no exemplo anterior, com o serviço de negócios. Também usamos a palavra-chavelateinit novamente, desta vez para a injeção de nosso serviço de negócios na classe de recurso.

5. Testando com Arquillian

Agora vamos implementar o teste de integração de nosso aplicativo com Arquillian e Shrinkwrap.

Para testar nosso aplicativo com o Arquillian, precisamos configurar o empacotamento e a implantação no contêiner de teste com o 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.example.jeekotlin")
          .addAsLibraries(kotlinRuntime)
          .addAsResource("META-INF/persistence.xml")
          .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
    }
    // more code
}

A implantação é semelhante às implantações típicas de Java para o teste de integração, mas contém algumas configurações adicionais. Aqui, usamos o Resolvedor Shrinkwrap Maven para recuperarkotlin-stdlib do repositório Maven. Em seguida, arquivamos no nosso WAR como uma biblioteca.

Depois disso, usamosHttpClient para executar solicitações CRUD em nossa API REST:

@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());
}

Neste exemplo, usamos@ArquillianResource para fornecer a URL para a API e, em seguida, serializamos o objetoStudent e fazemos o POST na API. Se tudo estiver correto e o objeto tiver sido criado no banco de dados, o status da resposta será 200 OK, o que afirmamos no final do teste.

6. Conclusão

Neste artigo, demonstramos como criar o aplicativo CRUD REST JPA no Kotlin, como implementá-lo, como executá-lo no contêiner Java EE e como testá-lo com o Arquillian. Como você pode ver, Kotlin e Java funcionam bem juntos, mas temos que fazer algum trabalho adicional para que isso aconteça.

O código-fonte completo para o exemplo está disponível emon GitHub.