Domainebene

Die Domainebene ist eine optionale Schicht, die sich zwischen der UI-Ebene und dem der Datenschicht.

<ph type="x-smartling-placeholder">
</ph> Wenn sie enthalten ist, stellt die optionale Domainebene Abhängigkeiten
    der UI-Ebene und
hängt von der Datenschicht ab.
<ph type="x-smartling-placeholder">
</ph> Abbildung 1: Die Rolle der Domainebene in der Anwendungsarchitektur

Die Domainschicht ist für die Kapselung der komplexen Geschäftslogik verantwortlich. einfache Geschäftslogik, die von mehreren ViewModels wiederverwendet wird. Diese Ebene ist optional, da diese Anforderungen nicht für alle Apps gelten. Sie sollten nur wenn es erforderlich ist, z. B. um Komplexität zu bewältigen oder die Wiederverwendbarkeit zu bevorzugen.

Eine Domainebene bietet folgende Vorteile:

  • Codeduplizierung wird vermieden.
  • Sie verbessert die Lesbarkeit in Klassen, die Domainebenenklassen verwenden.
  • Sie verbessert die Testbarkeit der App.
  • Durch die Aufteilung der Verantwortlichkeiten werden große Klassen vermieden.

Um diese Klassen einfach und kompakt zu halten, sollte jeder Anwendungsfall nur eine für eine einzelne Funktion verantwortlich ist und keine änderbaren Elemente enthalten, Daten. Stattdessen sollten Sie veränderliche Daten in Ihrer Benutzeroberfläche oder in den Datenebenen verarbeiten.

Namenskonventionen in diesem Leitfaden

In diesem Leitfaden sind Anwendungsfälle nach der einzelnen Aktion benannt, für die sie verantwortlich sind für die Sie angegeben haben. Die Konvention lautet wie folgt:

Verb im Präsens + Substantiv/Was (optional) + Anwendungsfall.

Beispiele: FormatDateUseCase, LogOutUserUseCase, GetLatestNewsWithAuthorsUseCase oder MakeLoginRequestUseCase.

Abhängigkeiten

In einer typischen App-Architektur passen die Anwendungsfallklassen zwischen ViewModels aus dem UI-Ebene und Repositories aus der Datenschicht Das bedeutet, dass Anwendungsfallklassen sind normalerweise von Repository-Klassen abhängig und kommunizieren mit der UI-Ebene, genau wie Repositories – mit Callbacks (für Java) oder Koroutinen (für Kotlin) Weitere Informationen hierzu finden Sie im Abschnitt zur Datenschicht. .

Vielleicht haben Sie in Ihrer App eine Anwendungsfallklasse, die Daten von einem Nachrichten- und einem Autorenverzeichnis und kombiniert diese:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository
) { /* ... */ }

Da Anwendungsfälle wiederverwendbare Logik enthalten, können sie auch für andere Zwecke genutzt werden. Cases. Es ist normal, dass auf der Domainebene mehrere Anwendungsfälle vorhanden sind. Für kann für den im Beispiel unten definierten Anwendungsfall der Parameter Anwendungsfall FormatDateUseCase, wenn mehrere Klassen aus der UI-Ebene von Zeit abhängig sind um die richtige Nachricht auf dem Bildschirm anzuzeigen:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
<ph type="x-smartling-placeholder">
</ph> GetRecentNewsWithAuthorsUseCase hängt von Repository-Klassen aus der
    der Datenschicht, sondern auch von FormatDataUseCase,
einer weiteren Anwendungsfallklasse,
    die sich auch auf der Domain-Ebene befindet.
Abbildung 2: Beispiel eines Abhängigkeitsdiagramms für einen Anwendungsfall, der von anderen Anwendungsfälle.

Anwendungsfälle in Kotlin aufrufen

In Kotlin können Sie Anwendungsfallklasseninstanzen wie Funktionen Definieren der invoke()-Funktion mit dem operator-Modifikator Weitere Informationen: Beispiel:

class FormatDateUseCase(userRepository: UserRepository) {

    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}

In diesem Beispiel können Sie mit der Methode invoke() in FormatDateUseCase Folgendes tun: Instanzen der Klasse so aufrufen, als wären sie Funktionen. Die Methode invoke() ist nicht auf eine bestimmte Signatur beschränkt, sondern können eine beliebige Anzahl von Parametern annehmen. und einen beliebigen Typ zurückgeben. Sie können invoke() auch mit verschiedenen Signaturen überladen. in Ihrem Kurs. In diesem Fall würden Sie den Anwendungsfall aus dem obigen Beispiel wie folgt aufrufen:

class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
    init {
        val today = Calendar.getInstance()
        val todaysDate = formatDateUseCase(today)
        /* ... */
    }
}

Weitere Informationen zum Operator invoke() finden Sie in der Kotlin-Dokumentation. Dokumentation.

Lebenszyklus

Anwendungsfälle haben keinen eigenen Lebenszyklus. Stattdessen beziehen sie sich auf den Kurs. der sie verwendet. Das bedeutet, dass Sie Anwendungsfälle über Klassen in der UI aufrufen können. aus Diensten oder der Klasse Application selbst. Weil Anwendungsfälle keine änderbaren Daten enthalten, sollten Sie eine neue Instanz eines Anwendungsfalls erstellen. jedes Mal, wenn Sie sie als Abhängigkeit übergeben.

Threading

Anwendungsfälle auf der Domainebene müssen hauptsicher sein. mit anderen Worten, sie müssen können Sie über den Hauptthread aufrufen. Anwendungsfallklassen mit langer Ausführungszeit blockieren, sind sie dafür verantwortlich, diese Logik in den entsprechenden Thread. Bevor Sie das tun, sollten Sie jedoch prüfen, Operationen besser in anderen Hierarchieebenen platziert werden. Normalerweise finden komplexe Berechnungen in der Datenschicht statt, um die Wiederverwendbarkeit oder Caching. Beispielsweise ist ein ressourcenintensiver Vorgang auf einer großen Liste besser und nicht in der Domain-Ebene, wenn das Ergebnis um sie auf verschiedenen Bildschirmen der App wiederzuverwenden.

Das folgende Beispiel zeigt einen Anwendungsfall, der seine Arbeit auf einem Hintergrund ausführt. Thread:

class MyUseCase(
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {

    suspend operator fun invoke(...) = withContext(defaultDispatcher) {
        // Long-running blocking operations happen on a background thread.
    }
}

Allgemeine Aufgaben

In diesem Abschnitt wird beschrieben, wie Sie gängige Aufgaben der Domainebene ausführen.

Wiederverwendbare einfache Geschäftslogik

Sie sollten die auf der UI-Ebene vorhandene wiederholbare Geschäftslogik in einer Anwendungsfallklasse. Das macht es einfacher, Änderungen überall dort anzuwenden, wo die Logik verwendet wird. Außerdem können Sie die Logik isoliert testen.

Betrachten Sie das zuvor beschriebene FormatDateUseCase-Beispiel. Wenn Ihr Unternehmen Anforderungen an zukünftige Änderungen der Datumsformatierung, müssen Sie lediglich an einem zentralen Ort zu ändern.

Repositories kombinieren

In einer Nachrichten-App hast du möglicherweise die Kurse NewsRepository und AuthorsRepository die Nachrichten- bzw. Autorendatenvorgänge abwickeln. Klasse Article die NewsRepository nur den Namen des Autors anzeigt, Sie möchten aber um weitere Informationen über den Autor auf dem Bildschirm anzuzeigen. Informationen zum Autor kann im AuthorsRepository abgerufen werden.

<ph type="x-smartling-placeholder">
</ph> GetLatestNewsWithAuthorsUseCase ist von zwei verschiedenen Repositorys abhängig
    Klassen aus der Datenschicht: NewsRepository und AuthorsRepository.
<ph type="x-smartling-placeholder">
</ph> Abbildung 3: Abhängigkeitsdiagramm für einen Anwendungsfall, der Daten aus mehreren Repositories.

Da die Logik mehrere Repositories umfasst und komplex werden kann, erstellen Sie eine GetLatestNewsWithAuthorsUseCase-Klasse, um die Logik aus das ViewModel und erleichtern die Lesbarkeit. Dadurch wird die Logik auch einfacher, isoliert getestet und in verschiedenen Teilen der App wiederverwendbar.

/**
 * 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
        }
}

Die Logik ordnet alle Elemente in der news-Liste zu. Auch wenn die Datenschicht Hauptthread nicht blockiert wird, da Sie nicht wissen, Elemente verarbeitet werden. Deshalb verlagert der Anwendungsfall die Arbeit in den Hintergrund. Standard-Dispatcher verwendet.

Andere Verbraucher

Abgesehen von der UI-Ebene kann die Domain-Ebene auch von anderen Klassen wiederverwendet werden, z. B. und der Application-Klasse. Wenn andere Plattformen wie das Fernsehen oder Wear die Codebasis für die mobile App freigeben, kann deren UI-Ebene ebenfalls um alle oben genannten Vorteile der Domain-Ebene zu nutzen.

Zugriffsbeschränkung für Datenschicht

Eine weitere Überlegung bei der Implementierung der Domainebene ist, ob Sie weiterhin direkten Zugriff auf die Datenschicht über die UI-Ebene ermöglichen oder über die Domain-Ebene hinweg.

<ph type="x-smartling-placeholder">
</ph> Die UI-Ebene kann nicht direkt auf die Datenschicht zugreifen. Sie muss die Domain-Ebene durchlaufen. <ph type="x-smartling-placeholder">
</ph> Abbildung 4: Abhängigkeitsdiagramm, das zeigt, auf welche UI-Ebene der Zugriff verweigert wird mit der Datenschicht.

Diese Einschränkung hat den Vorteil, dass dadurch verhindert wird, dass Ihre Benutzeroberfläche das Domain-Layer-Logik. Wenn Sie beispielsweise Analytics-Logging auf jedem Zugriffsanforderung an die Datenschicht.

Der möglicherweise erhebliche Nachteil besteht jedoch darin, dass Sie dadurch gezwungen sind, Anwendungsfälle hinzufügen, auch wenn es sich nur um einfache Funktionsaufrufe an die Datenschicht handelt was die Komplexität erhöhen kann.

Es empfiehlt sich, Anwendungsfälle nur bei Bedarf hinzuzufügen. Wenn Sie feststellen, dass Ihre Benutzeroberfläche auf Daten fast ausschließlich über Anwendungsfälle zugreift, ist es sinnvoll, nur auf diese Weise auf Daten zuzugreifen.

Die Entscheidung, den Zugriff auf die Datenschicht einzuschränken, ist letztendlich individuelle Codebasis, ob Sie strenge Regeln oder eine flexiblere Ansatz.

Testen

Beim Testen der Domain gelten allgemeine Richtlinien für Tests. Ebene. Für andere UI-Tests verwenden Entwickler in der Regel fiktive Repositories. ist es empfehlenswert, beim Testen der Domainebene auch gefälschte Repositories zu verwenden.

Produktproben

In den folgenden Google-Beispielen wird die Verwendung der Domainebene veranschaulicht. Sehen Sie sich diese Tipps in der Praxis an: