Nicolai Wolko
Symbolbild für modulare Softwarearchitektur: Vier verbundene 3D-Puzzleteile repräsentieren strukturierte Layer wie Adapter, Store, Utility und Domain.

ASU-D: Struktur für Angular-Services

Veröffentlicht am 14. Juni 2025 von Nicolai Wolko

Dieses Paper richtet sich an Entwicklerteams und Entwickler, die mit Angular Enterprise Applikationen schaffen und sich in Services verlieren.

Viele Services in Angular sind Sammelstellen geworden. Für alles, was sonst keinen Platz findet. Weil das Framework keine Vorgaben macht. Weil es bequem ist. Und weil es schnell funktioniert.

Was dabei entsteht, sieht man oft erst später. Wenn Projekte wachsen und neue Entwickler fragen, wo spezifischer Code denn genau sein sollte. Angular selbst ist ein sehr geführtes Framework. Für viele Fragen bietet es genau die eine Lösung, die Best Practice ist. Services hingegen wurden uns Entwicklern überlassen. Viele sind mit dieser plötzlichen Gestaltungsfreiheit überfordert und oft kommt der Moment, an dem viele nicht mehr wissen, wohin mit ihrem Code. Hier soll ASU-D einen Impuls bieten und helfen.

Diese Struktur ist nicht aus Lehrbüchern entstanden, sondern aus realem Frust. Aus echter Projektarbeit, bei der die Services schnell zur Halde und Endlösung für Code wurden, der scheinbar keinen Platz hatte. Dennoch bewegt sich ASU-D auf Schultern von Riesen. Es verbindet moderne Konzepte wie Domain Driven Design und Clean Architecture mit moderner Angular Entwicklung

ASU-D ist kein Framework. Es ist auch keine Regel oder Gebrauchsanleitung. Es ist ein Konzept, Services zu sehen. Und sie so zu schreiben, dass andere sie verstehen. Architektur sollte nicht mit Tools beginnen, sondern mit Konzepten und Sprache.

Die Zielgruppe sind Entwickler, die irgendwann gemerkt haben, dass „einfach mal ein Service“ keine Antwort ist. Teams, die nicht nur Code schreiben wollen, sondern Systeme bauen. Wer Angular kennt, wird sich wiederfinden. Wer mit anderen Frameworks arbeitet, wird Parallelen sehen.

Und wer meine Artikelserie "Architecting Angular Applications" kennt, weis: Es geht nicht um blindes Gehorchen, sondern um Konzepte und Werkzeuge die helfen sollen, täglich Architektur zu schaffen.

Ausgangslage

Wer hat noch nicht dieses Problem gehabt? Einen gigantischen services Ordner. Voll gestopft mit Services, welche keine klaren Verantwortlichkeiten haben. Teilweise sogar redundant. Der Code ist nicht sauber modular geschrieben, Refactoring wäre wahnsinnig aufwendig und gefährlich, also ergänzt man lieber einen weiteren Service. Ein Phänomen, dass man in der Praxis und bei grossen Enterprise Applikationen oft sieht.

Im Vergleich zu React oder Vue ist Angular sicher das Framework, dass den Entwickler am meisten führt. Angular liefert einen fertigen Werkzeugkasten in einem monolithischen Paket. Von Routing über Dependency Injection, klaren Rollen bis hin zu einer klaren vordefinierten Projektarchitektur ist Angular tatsächlich ein echtes Framework, nicht bloss Library.

Dennoch gibt es einen Bereich, bei dem Entwickler weitestgehend sich selbst überlassen sind. Den Services. Angular gibt klar vor, mit welchen Werkzeugen das UI dargestellt und strukturiert werden soll. Aber wie die Geschäftslogik strukturiert wird, da hört Angular auf. Es gibt Services und damit ist das Thema für Angular erledigt. In der Praxis wird es hier aber erst richtig spannend. Und viele sind überfordert, mit der plötzlichen Freiheit im Framework.

Es ist eine spannende Beobachtung, dass React und Vue hier einen anderen Fokus setzen. Mit Hooks und Composables herrscht hier ein Konzept, dass früh zur Modularisierung und Zerlegung in Teilprobleme motiviert. Angular gibt diesen Gedanken im Service Bereich nicht weiter. Dabei wäre es ein grosser Gewinn.

Die Idee hinter ASU-D

ASU-D ist entstanden, um Ordnung in das Service Chaos zu bringen. Zerschlagen und aufteilen war der nahe liegende Reflex, gemäss dem Teile und Herrsche Prinzip. Zwei konkrete Probleme treten in fast jedem Projekt auf und sollen gelöst werden.

Ein Service, der den User lädt, speichert, den Namen formatiert, Fehler verarbeitet und wenn er schon dabei ist am besten noch einen loading Indikator für das UI bereitstellt. In der Praxis ist das nicht selten, sondern Standard. Wie lässt sich das am besten lösen? Wer Clean Architecture kennt, erkennt die Muster. Es braucht ein Zentrum mit klarer Verantwortung. Und außen herum Schichten, die sich um Integration, nicht um Logik kümmern.

Es braucht also eine Schicht für Geschäftslogik, das wird schnell klar. Aber was landet sonst noch gerne in Services? Der State auf jeden Fall. Wo sollte er sonst sein, in den Komponenten wäre fatal. Ausserdem muss mit dem Backend kommuniziert werden oder mit anderen Libraries. Es braucht also Schnittstellen. Und zuletzt braucht es Hilfsfunktionen. Pure Funktionalität, wiederverwendbar und mit einer Verantwortlichkeit. Utilities also.

Es ergeben sich somit die 4 Layer von ASU-D:

4. Die vier Typen im Detail

ASU-D unterscheidet vier klar abgegrenzte Service-Typen. Jeder erfüllt einen spezifischen Zweck:

4.1 Adapter-Services

Adapter sind die Schnittstellen nach außen. Sie kommunizieren mit APIs, greifen auf Datenbanken oder externe Libraries zu. Sie treffen keine Entscheidungen, sie interpretieren keine Daten und halten keinen Zustand.

Ein Adapter soll Kommunikation ermöglichen oder verweigern. Beides gehört zu seiner Rolle. Was er nicht tun darf: Eigene Logik erfinden oder Fachlogik mischen. Wer einmal erlebt hat, wie komplexe Adapter mit Businessentscheidungen verunreinigt wurden, versteht schnell, warum diese Trennung nötig ist.

Beispiel: OrderApiAdapter

4.2 Store-Services

Stores halten den Zustand. Sie wissen, was im UI gerade aktiv ist. Welche Form geöffnet ist. Welche Daten erfasst wurden. Stores orchestrieren nicht, sie speichern und benachrichtigen.

Ein Store speichert, was gerade sichtbar oder relevant ist. Er entscheidet nicht, warum etwas sichtbar wird. Diese Trennung ist wichtig. Sonst verschwimmt die Grenze zwischen Zustand und Verhalten.

Beispiel: CheckoutStore

Stores lassen sich lokal oder global aufspannen. Angular bietet mit providers eine Möglichkeit, kontextspezifische Stores direkt in Komponenten einzubetten. Das hilft, ihre Reichweite klar zu halten.

4.3 Utility-Services

Utilities sind Werkzeuge. Klein, präzise, zustandslos. Sie helfen bei Formatierungen und Berechnungen. Eigene Daten halten oder sich in Geschäftsentscheidungen einmischen dürfen sie wiederum nicht.

Ein Utility ist am besten dann, wenn sie eine kleine abgeschlossene Aufgabe erfüllt. Wiederverwendbar und klar umrissen. Viele Entwickler lagern diese Helfer nicht aus und produzieren so Copy-Paste-Logik an jeder Ecke.

Beispiel: PriceFormatterUtility

4.4 Domain-Services

Die Domain ist das Herzstück. Hier steckt die Fachlogik. Hier werden Entscheidungen getroffen und Abläufe gesteuert. Ein Domain-Service orchestriert Adapter, liest aus Stores, nutzt Utilities und packt seine eigene Businesslogik obendrauf. Aber auch ein Domain-Service sollte nicht einfach endlos in seiner Komplexität wachsen. Er sollte einen wohl definierten Use Case erfüllen.

Ein Domain-Service enthält keine UI-Logik, hält keinen Zustand, macht keine HTTP-Calls. Er agiert auf einer höheren Ebene. Fachlich. Abstrakt. Aber konkret genug, dass man ihn einem Nicht-Entwickler erklären könnte.

Beispiel: CheckoutService

5. Struktur im Projekt

Wer ASU-D umsetzen will, braucht keine neue Library und kein zusätzliches Framework. Was es braucht, ist Disziplin und eine klare Struktur im Projekt. Allerdings sehen wir gerade faszinierende neue technologische Möglichkeiten. Nun da mit LLMs die Möglichkeit besteht Code semantisch zu erfassen, wäre es denkbar Linter oder Plugins zu schreiben, die ASU-D begleiten und verifizieren.

Aber zurück zur Struktur. Die einfachste Form: Vier Ordner auf der obersten Ebene des Feature-Moduls. Einer für jede Zone.

Jede Datei bekommt einen sprechenden Namen. Der Typ darf, muss aber nicht im Namen stehen. Wer einen CartStore sieht, versteht seine Rolle auch ohne zusätzliche Kommentare.

Gerade für neue Entwickler im Projekt ist diese Klarheit ein enormer Gewinn. Man erkennt sofort, in welchem Bereich des Codes man sich befindet. Der Einstieg wird leichter, die Wartung planbarer.

Natürlich lässt sich diese Struktur auch modular aufbauen. Statt globale Ordner können pro Feature dedizierte Strukturen entstehen - etwa features/checkout/stores oder features/user/adapters. Entscheidend ist nicht das Schema selbst, sondern das es mit Kohärenz und Disziplin gelebt wird.

6. Architektur und Disziplin

ASU-D ist kein dogmatisches Regelwerk, das genau in dieser Form befolgt werden muss. Es ist ein Vorschlag, wie sich Struktur auch dann halten lässt, wenn der Alltag hektisch wird. Wer Architektur ernst meint, braucht Konzepte, die sich in Alltags Situationen bewähren.

Das Modell lässt sich problemlos mit bekannten Prinzipien wie SOLID oder Separation of Concerns verbinden. Es ersetzt keine Theorie, es operationalisiert sie. Wer im Review nicht nur fragt, ob der Code „funktioniert“, sondern ob er seine Verantwortung kennt, der hat den Gedanken hinter ASU-D verstanden.

In vielen Teams fehlt eine gemeinsame Sprache. Begriffe wie Adapter, Store oder Domain-Service helfen, ohne Vorwurf über Struktur zu sprechen. Sie schaffen Klarheit, bevor aus pragmatischen Lösungen langfristige Schulden werden.

Genau deshalb funktioniert ASU-D auch ohne strenge Regeln. Es lebt davon, dass Entwickler sich auf ein gemeinsames Denken einigen. Architektur ist nicht das, was in einem Wiki steht. Architektur ist das, was Teams im Alltag durchhalten.

7. Fallstudie: Ein typischer UserService

Kaum ein Service ist so überladen wie der klassische UserService. In vielen Projekten sieht man Varianten, die alles erledigen, was irgendwie mit Benutzern zu tun hat: Daten laden, Login und Logout abwickeln, Berechtigungen prüfen, Claims parsen, UI-Zustände verwalten. Oft mit Seiteneffekten, Caching und Validierung inbegriffen. Der Service ist zentral, aber nicht mehr durchschaubar.

Ein typisches reduziertes Beispiel:


@Injectable({ providedIn: 'root' })
export class UserService {
  private user: User | null = null;
  private claims: number[] = [];
  private loading = false;

  constructor(private http: HttpClient) {}

  async login(username: string, password: string): Promise<boolean> {
    this.loading = true;
    try {
      const result = await this.http.post('/api/login', { username, password }).toPromise();
      this.user = result.user;
      this.claims = result.claims;
      return true;
    } catch {
      return false;
    } finally {
      this.loading = false;
    }
  }

  logout() {
    this.user = null;
    this.claims = [];
    this.http.post('/api/logout', {}).subscribe();
  }

  hasPermission(code: number): boolean {
    return this.claims.includes(code);
  }

  formatUserName(): string {
    return this.user ? `${this.user.firstName} ${this.user.lastName}` : '';
  }

  isLoading(): boolean {
    return this.loading;
  }
}

Was hier falsch läuft, sieht man nicht auf den ersten Blick. Der Code ist für dieses Beispiel sogar bewusst kompakt gehalten. In der Realität gibt es vom Hochladen des Profilbildes, über Token Refresh Mechanismen bis hin zur Validierung von Passwortregeln of unzählige weitere Features, die in den Service gepackt werden. Doch unser kleines Beispiel reicht und verletzt bereits fast jedes Prinzip moderner Architektur:

ASU-D: Die Zerlegung

Mit ASU-D wird der überladene Service in vier fokussierte Services zerlegt:

UserApiAdapter für Login und Logout

@Injectable({ providedIn: 'root' })
export class UserApiAdapter {
  constructor(private http: HttpClient) {}

  login(username: string, password: string): Promise<LoginResponse> {
    return this.http.post<LoginResponse>('/api/login', { username, password }).toPromise();
  }

  logout(): Promise<void> {
    return this.http.post<void>('/api/logout', {}).toPromise();
  }
}

UserStore für geladenen User, Loading-State und Claims

@Injectable({ providedIn: 'root' })
export class UserStore {
  private user = signal<User | null>(null);
  private claims = signal<number[]>([]);
  private loading = signal<boolean>(false);

  readonly user$ = this.user.asReadonly();
  readonly claims$ = this.claims.asReadonly();
  readonly loading$ = this.loading.asReadonly();

  setUser(user: User, claims: number[]) {
    this.user.set(user);
    this.claims.set(claims);
  }

  clear() {
    this.user.set(null);
    this.claims.set([]);
  }

  setLoading(state: boolean) {
    this.loading.set(state);
  }
}

PermissionCheckerUtility

@Injectable({ providedIn: 'root' })
export class PermissionCheckerUtility {
  hasClaim(claims: number[], code: number): boolean {
    return claims.includes(code);
  }
}

UserService orchestriert die Login- und Logout-Logik

@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(
    private adapter: UserApiAdapter,
    private store: UserStore
  ) {}

  async login(username: string, password: string): Promise<boolean> {
    this.store.setLoading(true);
    try {
      const response = await this.adapter.login(username, password);
      this.store.setUser(response.user, response.claims);
      return true;
    } catch {
      return false;
    } finally {
      this.store.setLoading(false);
    }
  }

  async logout(): Promise<void> {
    await this.adapter.logout();
    this.store.clear();
  }
}

Jeder dieser Services hat nun eine einzelne Verantwortung. Die APIs sind testbar, die Logik nachvollziehbar. Die UI kennt nur noch den Store, nicht den Domain-Service. Der Domain-Service kennt keine HTTP-Details mehr, sondern delegiert an den Adapter.

So entsteht keine Magie. Kein neues Pattern. Aber eine saubere, wartbare Architektur.

8. Was ASU-D nicht ist

ASU-D ist kein neues Framework. Es ist kein dogmatisches Modell. Und es ist ganz sicher kein Allheilmittel.

ASU-D ersetzt nicht Clean Architecture, sondern konkretisiert ihre Prinzipien für Angular-Entwickler. Es geht nicht darum, einen neuen Layer einzuziehen oder alles zu abstrahieren. Wer bereits mit Clean oder Hexagonal Architecture arbeitet, wird viele Überschneidungen erkennen. ASU-D ist eine für Angular gedachte Interpretation der genannten Architektur Prinzipien.

Auch ist ASU-D keine Bibliothek. Es gibt keinen Code zu installieren, kein Paket zu importieren. Das Modell lebt vollständig auf der Ebene der Semantik und des Projektdesigns. Wer Disziplin, Klarheit und gemeinsame Sprache ins Team bringen will, bekommt hier ein Konzept, keine technische Lösung.

Und nicht zuletzt: ASU-D kann Fehler nicht verhindern. Es zwingt niemanden, Dinge richtigzumachen. Es schützt nicht vor schlechter Architektur. Aber es macht es leichter zu strukturieren, über Verantwortung zu sprechen und Fehler früher zu erkennen.

Wie jede Architekturidee lebt auch ASU-D davon, dass sie verstanden und bewusst eingesetzt wird. Nicht als Regel, sondern als gelebte Praxis und Werkzeug.

9. Integration in bestehende Projekte

ASU-D funktioniert auch dort, wo bereits gewachsene Strukturen existieren. Man muss nicht alles neu schreiben. Oft reicht es, bei den bestehenden Services genauer hinzusehen und Verantwortung bewusst zu verschieben. Das Fallbeispiel geht genau diesen Weg und hat sich einen existieren Service neu strukturiert.

Refactoring in Schritten

Der pragmatischste Weg: Einzelne Services identifizieren, die zu viel Verantwortung tragen. Statt sie sofort aufzuspalten, beginnt man mit dem ersten Schritt. Utility auslagern und den API-Zugriff trennen. Der Store ist zwar sehr lohnen, aber in dem meisten Projekte der aufwendigste Schritt, da er sofort die UI Komponenten mit tangiert.

Solche Mikro-Refactorings lassen sich oft ohne Risiko umsetzen. Mit jedem Schritt entsteht mehr Übersicht und die Bereitschaft im Team wächst, diesen Weg weiterzugehen.

Minimalinvasive Einführung

Gerade in großen Systemen hilft es, ASU-D nicht als Regel, sondern als Denkmodell zu etablieren. Ein einzelnes Feature-Modul reicht. Dort wird sauber getrennt. Dort werden Begriffe wie Adapter und Domain-Service verwendet. Die anderen Module bleiben vorerst unberührt. So wurde ein präzedenz Fall geschaffen, an dem sich Entwickler künftig orientieren können.

Wer sieht, dass ASU-D keine Extra-Arbeit macht, sondern Arbeit spart, wird es übernehmen. Architektur muss sich im Alltag beweisen.

Kommunikation im Team

ASU-D kann helfen, Gespräche über Architektur zu entemotionalisieren. Es geht nicht mehr um richtig oder falsch, sondern um Verantwortlichkeiten. Wenn ein Team dieselben Begriffe verwendet, wird Architektur plötzlich diskutierbar. Das senkt die Einstiegshürde für Juniors und entlastet Seniors.

Deshalb gilt: Nicht alle überzeugen. Einfach anfangen.

10. FAQ und kritische Reflexion

Wann lohnt sich ASU-D nicht?

In sehr kleinen Projekten, mit wenigen Modulen und klaren Verantwortlichkeiten, ist die Aufteilung nach ASU-D oft überdimensioniert. Wenn ein Feature mit zwei Dateien überschaubar abbildbar bleibt, bringt eine künstliche Trennung mehr Overhead als Nutzen. Auch dort, wo kein Team arbeitet, sondern Einzelentwickler mit vollem Überblick, kann die Struktur zu viel Formalität einführen.

Wie geht man mit Mischformen um?

Es gibt Services, die schwer klar einzuordnen sind. Ein Formatter, der zusätzlich Zugriff auf das Backend benötigt. Ein Store, der eigene Methoden enthält, die Fachlogik ausführen. In der Realität gibt es Grauzonen. Entscheidend ist: Diese Stellen sind sichtbar. Wer ASU-D anwendet, erkennt Inkonsistenzen und kann sie gezielt auflösen oder dokumentieren. Perfektion ist nicht das Ziel. Das Ziel ist Bewusstheit. Wer eine Entscheidung verteidigen kann, der soll ruhig zu ihr stehen.

Signals & RxJS - ergänzend oder störend?

ASU-D trifft keine Aussage über das Reaktivitätsmodell. Signals und RxJS lassen sich in allen vier Zonen einsetzen. Wichtig ist nur, dass die Verantwortung sauber getrennt bleibt. Ein Adapter, der HTTP-Streams liefert, darf mit RxJS arbeiten. Ein Store, der lokale Zustände verwaltet, kann mit Signals effizient umgesetzt werden.

Lässt sich ASU-D auch ohne Angular anwenden?

Ja. ASU-D ist ein Strukturmodell für Anwendungen, nicht für Angular. Es wurde dort geboren, aber seine Prinzipien gelten auch in React, Vue oder Backend-Systemen. Wer über Services spricht, wer Verantwortung klären will, wer Teams eine gemeinsame Sprache geben möchte, kann ASU-D auch unabhängig vom Framework einsetzen.

11. Fazit

ASU-D ist keine neue Theorie. Es ist keine Revolution. Es ist die konsequente Anwendung moderner Architektur Prinzipien in Angular. Wer es nutzt, hat kein fertiges System, aber eine bessere Chance, eines zu bauen. Die vier Zonen helfen, Verantwortung sichtbar zu machen. Sie schaffen Klarheit, wo vorher alles einfach nur „ein Service“ war.

Gerade in Angular-Projekten, wo Services oft diffus und beliebig werden, setzt ASU-D einen Kontrapunkt. Kein Dogma. Aber eine klare Sprache und eine konkrete Praxis.

Die Erfahrung zeigt: Wer beginnt, zwischen Adapter und Domain, zwischen Store und Utility zu unterscheiden, schreibt anders. Architektur entsteht nicht mehr nebenbei, sondern wird sichtbar.

ASU-D versteht sich als Empfehlung und Gedankenanstoss. Ich freue mich jederzeit über Feedback. Gerne aktualisiere ich das Whitepaper auch mit Erfahrungswerten und neuen Erkenntnissen. Einfach per Mail an: kontakt@nicolaiwolko.ch