A camada de domínios é uma camada opcional que fica entre a camada da IU e a camada de dados.
A camada de domínios é responsável por encapsular a lógica de negócios complexa ou simples que é reutilizada por vários ViewModels. Essa camada é opcional, porque nem todos os apps vão ter esses requisitos. Use-a apenas quando necessário, por exemplo, para lidar com a complexidade ou favorecer a reutilização.
Uma camada de domínios oferece estes benefícios:
- Evita a duplicação de código.
- Melhora a legibilidade em classes que usam classes da camada de domínios.
- Melhora a capacidade de teste do app.
- Evita classes grandes ao permitir a divisão das responsabilidades.
Para manter as classes simples e leves, cada caso de uso precisa ter responsabilidade apenas sobre uma única funcionalidade e não pode conter dados mutáveis. Em vez disso, você precisa gerenciar dados mutáveis na IU ou nas camadas de dados.
Convenções de nomenclatura neste guia
Neste guia, os casos de uso recebem o nome da única ação pela qual são responsáveis. A convenção é esta:
verbo no presente + substantivo/o que (opcional) + UseCase.
Por exemplo, FormatDateUseCase
, LogOutUserUseCase
,
GetLatestNewsWithAuthorsUseCase
ou MakeLoginRequestUseCase
.
Dependências
Em uma arquitetura de app típica, as classes de caso de uso se encaixam entre ViewModels da camada de IU e repositórios da camada de dados. Isso significa que as classes de caso de uso geralmente dependem das classes de repositório e se comunicam com a camada da interface da mesma maneira que os repositórios: usando callbacks (em Java) ou corrotinas (em Kotlin). Para saber mais sobre isso, consulte a página sobre a camada de dados.
Por exemplo, no app, é possível ter uma classe de caso de uso que busque dados de um repositório de notícias e repositório de autor e os combine:
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository
) { /* ... */ }
Como os casos de uso contêm lógica reutilizável, eles também podem ser usados
por outros casos. É normal haver vários níveis de casos de uso na camada de domínios. Por
exemplo, o caso de uso definido no exemplo abaixo pode usar o
FormatDateUseCase
se várias classes da camada de IU dependem de fusos
horários para mostrar a mensagem adequada na tela.
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
Casos de uso de chamadas no Kotlin
No Kotlin, você pode fazer com que seja possível chamar as instâncias de classe de caso de uso como funções,
definindo a função invoke()
com o modificador operator
. Veja o exemplo
abaixo:
class FormatDateUseCase(userRepository: UserRepository) {
private val formatter = SimpleDateFormat(
userRepository.getPreferredDateFormat(),
userRepository.getPreferredLocale()
)
operator fun invoke(date: Date): String {
return formatter.format(date)
}
}
Nesse exemplo, o método invoke()
no FormatDateUseCase
possibilita
chamar instâncias da classe como se fossem funções. O método invoke()
não está
restrito a uma assinatura específica. Ele pode usar qualquer número de parâmetros
e retornar qualquer tipo. Também é possível sobrecarregar o operador invoke()
com assinaturas diferentes
na classe. O caso de uso do exemplo acima é chamado desta maneira:
class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
init {
val today = Calendar.getInstance()
val todaysDate = formatDateUseCase(today)
/* ... */
}
}
Para saber mais sobre o operador invoke()
, consulte os documentos
do Kotlin (em inglês).
Ciclo de vida
Os casos de uso não têm ciclo de vida próprio. Em vez disso, eles têm escopo definido para a classe
que os usa. Isso significa que você pode chamar casos de uso das classes na camada
da IU, de serviços ou da própria classe do Application
. Como os casos de uso
não podem conter dados mutáveis, é preciso criar uma nova instância de uma classe de caso de uso
sempre que a transmitir como uma dependência.
Linha de execução
Os casos de uso da camada de domínios precisam ter proteção para linha de execução principal. Em outras palavras, é preciso que seja seguro para eles fazerem chamadas da linha de execução principal. Se as classes de caso de uso realizam operações de bloqueio de longa duração, elas são responsáveis por mover essa lógica para a linha de execução adequada. No entanto, antes de fazer isso, confira se essas operações de bloqueio podem estar melhor posicionadas em outras camadas da hierarquia. Normalmente, cálculos complexos ocorrem na camada de dados para incentivar a reutilização ou o armazenamento em cache. Por exemplo, uma operação com uso intensivo de recursos em uma lista grande fica melhor posicionada na camada de dados do que na camada de domínios quando o resultado precisa ser armazenado em cache para ser reutilizado em várias telas do app.
O exemplo abaixo mostra um caso de uso que realiza o trabalho em uma linha de execução em segundo plano:
class MyUseCase(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(...) = withContext(defaultDispatcher) {
// Long-running blocking operations happen on a background thread.
}
}
Tarefas comuns
Esta seção descreve como executar tarefas comuns da camada de domínio.
Lógica de negócios simples e reutilizável
É necessário encapsular a lógica de negócios reproduzível presente na camada de IU em uma classe de caso de uso. Isso facilita a aplicação de mudanças em qualquer lugar que a lógica for usada. Isso também possibilita que você teste a lógica de forma isolada.
Considere o exemplo do FormatDateUseCase
descrito anteriormente. Caso seus requisitos
comerciais relacionados à formatação de data mudem no futuro, você só vai precisar
mudar o código em um local centralizado.
Combinar repositórios
Em um app de notícias, você pode ter as classes NewsRepository
e AuthorsRepository
, que processam notícias e operações de dados do autor, respectivamente. A classe Article
que o NewsRepository
expõe contém apenas o nome do autor, mas você quer
mostrar mais informações sobre o autor na tela. As informações do autor
podem ser encontradas no AuthorsRepository
.
Como a lógica envolve vários repositórios e pode se tornar complexa, você
cria uma classe GetLatestNewsWithAuthorsUseCase
para abstrair a lógica do
ViewModel e a tornar mais legível. Isso também torna a lógica mais reutilizável e fácil de
testar isoladamente em diferentes partes do app.
/**
* This use case fetches the latest news and the associated author.
*/
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(): List<ArticleWithAuthor> =
withContext(defaultDispatcher) {
val news = newsRepository.fetchLatestNews()
val result: MutableList<ArticleWithAuthor> = mutableListOf()
// This is not parallelized, the use case is linearly slow.
for (article in news) {
// The repository exposes suspend functions
val author = authorsRepository.getAuthor(article.authorId)
result.add(ArticleWithAuthor(article, author))
}
result
}
}
A lógica mapeia todos os itens na lista news
. Assim, mesmo que a camada de dados tenha proteção para linha de execução principal,
esse trabalho não deve bloquear a linha de execução principal porque você não sabe
quantos itens vão ser processados. É por isso que o caso de uso move o trabalho para uma linha de execução
em segundo plano usando o agente padrão.
Outros consumidores
Além da camada de IU, a camada de domínios pode ser reutilizada por outras classes, como
serviços e a classe do Application
. Além disso, se outras plataformas, como TV
ou Wear, compartilharem bases de código com o app para dispositivos móveis, a camada de interface também vai poder reutilizar os casos
de uso para aproveitar todos os benefícios da camada de domínios mencionados acima.
Restrição de acesso à camada de dados
Outra consideração ao implementar a camada de domínios é se você ainda precisa permitir o acesso direto à camada de dados da IU ou forçar tudo na camada de domínios.
Uma vantagem de fazer essa restrição é que ela impede que a interface ignore a lógica da camada de domínios, por exemplo, quando você executa registros de análise em cada solicitação de acesso à camada de dados.
No entanto, a desvantagem possivelmente significativa é que isso força você a adicionar casos de uso, mesmo quando são apenas chamadas de função simples para a camada de dados, o que pode aumentar a complexidade com pouco benefício.
Uma boa abordagem é adicionar casos de uso somente quando necessário. Se você acha que a camada de interface está acessando dados por casos de uso quase exclusivamente, talvez seja recomendável acessar dados apenas dessa maneira.
Por fim, a decisão de restringir o acesso à camada de dados depende da sua base de código individual e de você preferir regras rígidas ou uma abordagem mais flexível.
Testes
As diretrizes gerais de teste se aplicam ao testar a camada de domínios. Para outros testes de interface, os desenvolvedores geralmente usam repositórios falsos. Essa é uma prática recomendada ao testar a camada do domínios.
Exemplos
Os exemplos do Google abaixo demonstram o uso de uma camada de domínios. Acesse-os para conferir a orientação na prática:
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Camada de dados
- Produção do estado da interface