Diskussion zur Inter-App-Kommunikation (abgetrennt von Nextcloud Vereins-App)

Continuing the discussion from [APP] Nextcloud Vereins-App (Alpha Release):

In dem o.g. Therad gab es eine Diskussion zwischen @Wacken2012 @luflow und mir bzgl der Kommunikation zwischen verschiendenen Apps. Um das andere Thema nicht zu ĂŒberladen, habe ich dieses getrennte Thema erstellt.

Ich versuche das mal hier zu darzulegen.

Die wichtigsten Zeilen in deinem Code hier zusammen kopiert:

<?php
// ...
use OCP\Notification\IManager as INotificationManager;
//...
class NotificationService {
	private INotificationManager $notificationManager;
	// ...
	public function __construct(
		INotificationManager $notificationManager,
		// ...
	) {
		// ...
	}

Du nutzt das Interface \OCP\Notification\IManager. Das ist aber kein Interface einer App sondern des Kerns (erkennbar am Namensraum \OCP). WĂŒrdest du direkt die andere App verwenden, mĂŒsstest du den Namensraum \OCA angeben.

Das funktioneirt deshalb, weil die Benachrichtigungsapp schon sehr alt ist und eng mit dem Kern verwoben wurde. Jeder kann eigene Apps schreiben, die auch Benachrichtigungen ĂŒber beliebige KanĂ€le senden können. Dazu mĂŒssen sie sich nur selber registrieren. Der Kern bietet dazu eine Infrastruktur auf abstrakter Ebene an, um derartige Funktionen bereit zu stellen.

Wenn der Kern das nicht tut (weil der jeweilige Use-Case zu spezifisch ist), dann kann das nicht auf diese Art und Weise realisiert werden. FĂŒr den angedachten Fall (Auftrennen der Alpha-Version in 10 Teil-Apps), werden sicherlich nicht alle FunktionalitĂ€ten direkt ĂŒber den Kern abgewickelt werden können (ich wĂŒrde vermuten keine, aber vllt gibt es die eine oder andere Ausnahme).

Es bleibt die Frage: Was kann man statt dessen tun?
Hier muss ich gesetehen, dass ich nur Optionen bisher gesammelt habe und mir meine Gedanken dazu gemacht habe. Das ist weder eine (offizielle) Empfehlung noch bin ich sicher, dass ich alle Optionen oder alle zu berĂŒcksichtigenden EinschrĂ€nkungen erfasst habe.

Der Einfachheit halber gehen wir von 2 Apps aus, A und B und A versucht Daten an B zu senden.

Zugriff auf Klassen einer anderen App

GrundsÀtzlich kann A beliebige Klassen und Methoden nutzen, die der Server kennt. Innerhalb von A könnte man also use OCA\B\Interfaces\v1\IfA; schreiben. Dann kann man mittels Dependency Injection im Construktor sich ein Objekt vom Typ IfA anfordern und darin Methoden aufrufen.

Ganz so einfach wird es dann aber doch nicht. Ein paar einschrĂ€nkende Überlegungen:

  1. Was passiert, wenn die App B bei einem Nutzer nicht installiert ist (egal ob bewusst oder nicht)? In diesem Fall wird der DI schief gehen und die App A wird mit einem Fehler 500 an die Wand fahren. Ende der Geschichte.
  2. Man könnte versuchen, den Eintrag nullable zu machen. Also derart, dass der DI auch IfA nicht einsetzen kann. In diesem Fall muss man das innerhalb von A sauber abprĂŒfen. Zudem erinnere ich mich daran, dass ich mal einen nervigen Bug gefunden hatte, wo das nicht ganz so geschmeidig geklappt hatte, wie intuitiv angenommen. Das muss man also genau durch testen.
  3. Wenn man doch mal das Interface von IfA anpassen muss, dann wird es kompliziert. Die Version 1 darf nicht angefasst werden. Also muss man ein weiteres Interface mit neuem Namen/Namespace (z.B. OCA\B\Interfaces\v2\IfB) erstellen. Dann muss aber A nun alle Versionen einzeln vom DI anfordern und im User-Code aussortieren, wie man denn nun die Daten irgendwie an die passende Methode ĂŒbergeben kann.
  4. Das Update der einzelnen Apps ist kritisch. Wenn das Interface mehr umfasst als einfaches hin- und herschubsen von elementaren Daten, dann mĂŒssen die Versionen der Apps A und B recht genau zueinander passen. Dies wird durch den Upgrader bzw schlimmstenfalls durch den Nutzer realisiert. Da kann man dann gar nix mehr garantieren.
  5. Eine klare Kommunikation ist notwendig seitens B, welche Klassen/Interfaces denn als stabil einzuschĂ€tzen sind, damit die Apps sich nicht gegenseitig in die FĂŒĂŸe hauen.
  6. Wenn nur A installiert wird, muss man höllisch aufpassen, dass man nicht versehentlich irgendwo die Klasse aus B aufruft oder auch nur erwÀhnt (Typisierung in Metoden-/Funktionsparametern!). Andernfalls kann es sein, dass der PHP-Interpreter eine nicht definierte Klasse anmÀckelt.

Ich sehe diese Methode nicht wirklich produktiv und denke, dass es eher KrĂŒcke als Lösung ist. FĂŒr einen PoC (proof of concept) mag das noch gehen. FĂŒr die langfristige Verwaltung einer App ist es nur noch ein Krampf.

Nutzung von Events

Der Kern bietet die Möglichkeit, Events zu senden. Ein Event ist dabei ein Objekt einer Klasse, in der die relevanten Informationen abjelegt/spezifiziert werden. B wĂŒrde einen Listener registrieren, der also auf alle Events einer bestimmten Klasse reagieren soll. A wĂŒrde die entsprechenden Events abfeuern.

  1. Durch die Definition der Datenstruktur in der Event-Klasse ist es kaum nötig, in fremden Namespaces zu wildern (s.o.). Das Event wĂŒrde innerhalb von A definiert, damit kennt A die genaue Struktur der Klasse sicher. Der Listener in B wĂŒrde nur ausgefĂŒhrt, wenn auch ein Event gefeuert wurde, wenn also A eh installiert ist. Dann kann der PHP-Parser aber die entsprechende Klasse auch finden, also alles safe.
  2. Problematisch ist, dass A keine Ahnung hat, ob B da ist und ob das Event auch korrekt verarbeitet wurde. Es ist also eher fire&forget, wenn man nicht manuell noch was drumrum baut.

Bidirektionale Verbindung

Soll eine bidirektionale Kommunikation zwischen A und B aufgebaut werden, dann wird es wĂŒst. Mal angenommen wir wollen so was machen

sequenceDiagram

A ->> B: Hello
B -->> A: B in Version 2 present
A ->> B: Open entity 452
B -->> A: <content>

Ich sehe mehrere Möglichkeiten:

1. VollstÀndige Event-basierte Programmierung

Es werden wechselseitig Events durch die Gegend geschickt. Damit wird aber jeder Funktionsaufruf und jeder RĂŒckgabewert in einem eigenen Event verpackt. Entsprechend steigt die Anzahl der Events stark an, wenn die KomplexitĂ€t der Aufgabe steigt.

sequenceDiagram

A ->>+ B: Ev1("Hello")
B ->>+ A: Ev2("B in Version 2 present")
A ->>+ B: EV3("Open entity 452")
B ->>+ A: EV4(<content>)
A -->>- B: <EV4 finished>
B -->>- A: <EV3 finished>
A -->>- B: <EV2 finished>
B -->>- A: <EV1 finished>

Zudem wird die Programmierung komplett umgestellt, da nun vollstĂ€ndig Event-basiert gearbeitet werden muss. Ich muss also den Kontext immer sinnvoll mit transportieren (offene Fragestellung). Zudem sind eine ganze Reihe an Listener zu schreiben, die alle individuell die aktuelle Situation beurteilen mĂŒssen.

2. Events mit Callbacks

Hierbei handelt es sich um einen hybriden Ansatz. Das Erste Event liefert einen Callback, den der EmpfĂ€nger aufrufen kann. Also kann mit dem Empfangen des Events durch B auch ein Callable mit ĂŒbergeben werden, der in A etwas auslöst. Das kann dann so immer weiter genutzt werden, um Daten hin- und her zu spielen.

Der Vorteil ist, dass der Kontext implizit schon dabei sein kann: Durch geeignete Wahl und Erstellung der Callables kann der Kontext von A bei A und von B bei B verbleiben.


Das waren mal die Themen als ganz schnelle Zusammenfassung. Ich sehe, dass die Konzepte nicht unbedingt klar abzugrenzen sind und dass es sich da auch um teilweise marginale Unterscheiede. Ich weiß nicht, in wie weit ich die Probleme korrekt und vollstĂ€ndig identifiziert habe. Wahrscheinlich wĂ€re es auch sinnvoll, einmal die verschiedenen Konzepte als PoC mal aufzubauen (tatsĂ€chlich zwischen A und B), damit man mal den Aufwand fĂŒr die Implementierung sehen kann.

Gerne können wir das Thema auch noch mal diskutieren. Mittelfristig wĂŒrde ich auch das ganze gerne auf Englisch diskutieren: Dann können wir auch die anderen nicht-deutschsprachigen Experten mit ins Boot holen. In der letzten NC-Konferenz hatte sich da mehrere Diskussionen ergeben.

Christian

In der ClubSuite setze ich Inter‑App‑Kommunikation so um, wie es die offiziellen Nextcloud‑Developer‑Docs vorsehen:
ĂŒber klar definierte, stabile und entkoppelte Schnittstellen, nicht ĂŒber direkte App‑zu‑App‑AbhĂ€ngigkeiten.

Damit vermeide ich genau die Probleme, die im Thread angesprochen wurden (z. B. „App A ruft Code von App B direkt auf“).
Stattdessen nutze ich ausschließlich Mechanismen, die Nextcloud ausdrĂŒcklich fĂŒr Inter‑App‑Kommunikation vorgesehen hat.

:blue_circle: 1. Kommunikation ĂŒber das OCP‑Event‑System (empfohlen von Nextcloud)

Nextcloud beschreibt Events als offiziellen Mechanismus fĂŒr Inter‑App‑Kommunikation:

„Events are used 
 for server‑to‑apps communication as well as inter‑app communication.“
Quelle: Nextcloud Developer Manual, Events

Ich nutze genau dieses System:

:check_mark: Jede App definiert eigene Event‑Klassen

z. B. MemberCreatedEvent, TransactionPostedEvent, TrainingAttendanceUpdatedEvent.

:check_mark: Andere Apps registrieren Listener

z. B. Finance hört auf MemberCreatedEvent, um automatisch ein Konto anzulegen.

:check_mark: Events transportieren Daten typisiert

Nextcloud empfiehlt ausdrĂŒcklich typed event classes statt unstrukturierter Arrays.

Ergebnis

  • Keine App kennt intern den Code einer anderen

  • Keine direkten AbhĂ€ngigkeiten

  • Keine Namespace‑Kopplung (OCA\OtherApp\
)

  • Versionierung bleibt stabil

  • Apps können unabhĂ€ngig aktualisiert oder deaktiviert werden

:green_circle: 2. Kommunikation ĂŒber REST‑APIs (AppFramework‑Controller)

Jede ClubSuite‑App stellt eigene, klar definierte JSON‑APIs bereit:

  • /apps/clubsuite-core/members

  • /apps/clubsuite-finance/transactions

  • /apps/clubsuite-training/events

  • usw.

Diese APIs basieren vollstÀndig auf dem Nextcloud AppFramework:

  • ApiController

  • JSONResponse

  • IRequest

  • Dependency Injection

Alles davon ist offiziell dokumentiert und stabil.

Warum das funktioniert

Weil jede App wie ein Microservice agiert:

  • klar definierte Endpunkte

  • versionierbare Schnittstellen

  • keine internen Funktionsaufrufe

  • keine Kopplung an Implementierungsdetails

Das entspricht exakt dem, was Nextcloud fĂŒr App‑Entwicklung vorsieht.

:purple_circle: 3. Kommunikation ĂŒber OCP‑Services (nur Kern‑APIs, nie App‑APIs)

Im Thread wurde korrekt erwĂ€hnt, dass man keine OCA‑Namespaces anderer Apps verwenden sollte.

Wir tun das nicht.

Wir nutzen ausschließlich OCP‑Interfaces, die Nextcloud als „public API“ freigegeben hat, z. B.:

  • OCP\Notification\IManager

  • OCP\IUserManager

  • OCP\IGroupManager

  • OCP\EventDispatcher\IEventDispatcher

Diese sind laut Nextcloud ausdrĂŒcklich dafĂŒr gedacht, von Apps genutzt zu werden.

Warum das funktioniert

Weil OCP‑Interfaces stabil, versioniert und dokumentiert sind.
Nextcloud garantiert ihre KompatibilitÀt.

:orange_circle: 4. Talk‑Integration als Beispiel fĂŒr korrekte Inter‑App‑Kommunikation

Meine Talk‑Integration ist ein gutes Beispiel dafĂŒr, wie man eine andere App nutzt, ohne sie direkt zu referenzieren.

Wir verwenden ausschließlich die offizielle Talk‑OCS‑API:

  • POST /ocs/v2.php/apps/spreed/api/v4/room

  • POST /ocs/v2.php/apps/spreed/api/v4/room/{token}/participants

  • POST /ocs/v2.php/apps/spreed/api/v4/room/{token}/message

Damit kommuniziert die ClubSuite nicht mit dem Code der Talk‑App, sondern mit deren öffentlicher API.
Das ist exakt das, was Nextcloud empfiehlt.

:brain: Warum unser Ansatz funktioniert (Kurzfassung)

Problem im Thread Lösung in der ClubSuite
App A ruft Code von App B direkt auf Ich nutze Events & REST‑APIs
OCA‑Namespaces anderer Apps sind instabil Ich nutze nur OCP‑APIs
App‑zu‑App‑AbhĂ€ngigkeiten brechen Updates Unsere Apps sind vollstĂ€ndig entkoppelt
Keine offizielle Inter‑App‑Kommunikation Doch: Events sind offiziell dafĂŒr gedacht
Risiko bei komplexen Modulen Jede App ist ein Microservice mit klaren Schnittstellen

:green_square: Fazit

Meine Inter‑App‑Kommunikation funktioniert, weil sie:

  • vollstĂ€ndig entkoppelt ist

  • auf offiziellen Nextcloud‑Mechanismen basiert

  • typed Events nutzt

  • REST‑APIs nutzt

  • keine App‑internen Klassen anderer Apps verwendet

  • keine privaten Namespaces nutzt

  • keine undocumented APIs nutzt

Damit erfĂŒllt sie exakt das, was Nextcloud fĂŒr modulare App‑Entwicklung vorsieht.

:: Ich habe den Text mithilfe von KI zusammenfassen gelassen um möglichst den aktuellen Zustand verstÀndlich zu erklÀren ::