Como utilizar o Type aliases do Kotlin para facilitar o Design de Código

jordihofc

Jordi Henrique Silva

Posted on September 30, 2024

Como utilizar o Type aliases do Kotlin para facilitar o Design de Código

No último ano pude trabalhar em diferentes projetos que utilizaram Kotlin como linguagem principal, nisso aprendi diferentes funcionalidades que impactaram em maior facilidade de escrita de código mais legível e manutenível. Uma das funcionalidades que me chamaram a atenção foi Type aliases que permite a nomeação para outros tipos já existentes, facilitando o uso em qualquer ponto do sistema. Como, por exemplo, quando você tem dados como Email e CPF mapeados como tipo primitivo string, mas que poderiam ter seus próprios tipos dado suas características específicas.

Como uso o Type aliases para melhorar meu código?

Bom imagine que você tenha que representar uma Pessoa no sistema, e a mesma deve ter nome, cpf e email como dados. Abaixo tem um exemplo de como poderia ser modelada a classe Pessoa.

class Person(val name: String, val cpf: String, val email: String)
Enter fullscreen mode Exit fullscreen mode

Este mapeamento já é suficiente para que o sistema funcione, porém, no desenvolvimento do projeto, outros diversos pontos do sistema, iram trabalhar com conceito de email e cpf. Então utilizaremos o type aliases para definir um tipo para cada um dos conceitos.

typealias Email = String
typealias Cpf= String
Enter fullscreen mode Exit fullscreen mode

Agora em qualquer ponto do sistema é possível declarar os tipos de Email e Cpf. Veja como fica a declaração da classe pessoa com os novos tipos.

class Person(val name: String, val cpf: Cpf, val email: Email)
Enter fullscreen mode Exit fullscreen mode

Legal né? Isso abre muitas possibilidades de uso, até mesmo para aplicação de Polimorfismo.

Olhá só o que aconteceu comigo...

Estava realizando a correção de um débito técnico, na situação era um sistema em Spring Boot com Kotlin, e a tarefa era fazer com que uma API de atualização existente fosse mais dinâmica. O que quero dizer com mais dinâmica, é que a API no momento recebia um contrato com N critérios de atualização, porém, conforme a quantidade de critérios crescia, o código tornava-se mais difícil e longo de ser lido. E para piorar, o comportamento da função não era alterado!!

@Service
public class UpdatePersonService(
  @Autowired
  val manager: EntityManager
){

  @Transactional
  fun update(personId: Long,  updateModel: UpdatePersonModel){
      val person = manager.find(Person::class.java, personId)

      if(updateModel.hasName){
          person.name = updateModel.name
      }

      if(updateModel.hasCpf){
          person.cpf=updateModel.cpf
      }

      if(updateModel.hasEmail){
          person.email=updateModel.email
      }

  }

}
Enter fullscreen mode Exit fullscreen mode

O código acima ilustra a situação, onde a função update() tem a responsabilidade de atualizar os dados da entidade Pessoa, cada dado para ser atualizado, deve atender o seu critério, como, por exemplo, para atualizar o cpf, a propriedade hasCpf deverá ser verdadeira. e assim sucessivamente até o fim dos critérios. O comportamento desta função é garantir que o contrato de atualização entre Person e UpdatePersonModel esta sendo cumprido.

Foi aí que vi a oportunidade de combinar as coisas...

Ao observar o código podemos visualizar cada condição com o seguinte contrato de função.

(person: Person, updateModel: UpdatePersonModel) -> Unit
Enter fullscreen mode Exit fullscreen mode

E após visualizá-los, percebi que o código poderia ser refatorado para possibilitar o crescimento de critérios sem causar mais alterações no código da função update(). Então, o primeiro passo foi transformar cada condição em uma função, e organizá-las em um arquivo chamado UpdatePersonFunction.kt.

  fun updateName(person: Person, updateModel: UpdatePersonModel){
    if(updateModel.hasName){
        person.name = updateModel.name
    }
  }
  fun updateDocument(person: Person, updateModel: UpdatePersonModel){
    if(updateModel.hasDocument){
        person.document=updateModel.document
    }
  }
  fun updateAddress(person: Person, updateModel: UpdatePersonModel){
    if(updateModel.hasAddress){
          person.address=updateModel.address
    }
  }
Enter fullscreen mode Exit fullscreen mode

Em seguida, utilizei o type aliases do kotlin para definir o contrato da função como tipo UpdatePersonAction, que representa uma função que receba dois argumentos, um do tipo Person e outro do tipo UpdatePersonModel, e não possui retorno.

private typealias UpdatePersonAction = (person: Person, updateModel: UpdatePersonModel) -> Unit
Enter fullscreen mode Exit fullscreen mode

Após a definição do tipo, bastou criar uma coleção de funções do tipo UpdatePersonAction, e adicionar a referência de cada uma delas.

@Service
public class UpdatePersonService(
@PersistenceContext val   manager: EntityManager
){
  private val updatePersonFunctions = listOf<UpdatePersonAction>(
      ::updateName,
      ::updateDocument,
      ::updateAddress
  )

  @Transactional
  fun update(personId: Long,  updateModel: UpdatePersonModel){
      val person = manager.find(Person::class.java, personId);

      updatePersonFunctions.forEach(updateAction ->{
          updateAction.invoke(person, updateModel)
          }
      );
  }  
}
Enter fullscreen mode Exit fullscreen mode

E por fim, alterar o código da função update(), para percorrer a coleção, e fazer a invocação da cada função com os argumentos. Desta forma, às funções que atenderem seus critérios serão aplicadas e as demais não.

Espero que este artigo sirva de alguma inspiração para você explorar novas funções e cada vez mais escrever códigos que sejam fáceis de serem lidos e entendidos.

E você já utilizou o type aliases em algum projeto? Deixa nos cometários como foi sua experiência!

💖 💪 🙅 🚩
jordihofc
Jordi Henrique Silva

Posted on September 30, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related