PostGraphile Subscriptions

Mit Subscriptions kann man über Events und Änderungen in der Datenbank informiert werden. Ein offensichtlicher Anwendungsfall sind Notifications.

Wenn wir in unserem Code-Beispiel einer Blogging-App (ihr wisst schon, unsere PostGraphile-Serie*) bleiben, könnte man dem User zum Beispiel die Möglichkeit geben, über neue Blogposts informiert zu werden. Oder falls man als Betreiber der App sicherstellen will, dass Spammer nicht einfach posten können, könnte man einbauen, dass Blogposts immer erst freigeschaltet werden müssen. In so einem Fall will man natürlich eine Nachricht bekommen, wenn ein Blogpost freigeschaltet werden will.

* PostGraphile in-depth


PostGraphile Subscriptions

Subscriptions können einfach aktiviert werden, indem man die --subscriptions (oder subscriptions: true) -Flag an PostGraphile gibt. Es wird aber empfohlen, das pgPubSub-Plug-in zu verwenden. Es macht sich die PostgreSQL-eigenen LISTEN/NOTIFY Commands zu nutzen. Das Plug-in gibt uns zusätzlich zu der Postgraphile-eigenen --subscriptions-Flag die --simple-subscriptions-Flag, die sich gut eignet, um Dinge auszuprobieren.

Im Folgenden gehen wir davon aus, dass pgPubSub installiert ist.

Weiterhin gehen wir davon aus, dass das Wissen aus den bisherigen Teilen der Serie bekannt ist. Wenn also unbekannte Begriffe auftauchen (wie zum Beispiel GraphiQL oder Plug-ins), ohne dass sie näher erklärt werden, dann wurde das in früheren Teilen der Serie besprochen.

Los geht’s!

Um Subscriptions zu nutzen, müssen wir einige Dinge tun. Also wollen wir mal loslegen:

PSQL Trigger

Als erstes brauchen wir in unserer PostgreSQL-Datenbank einen Trigger.

Trigger sind grob erklärt Funktionen, die unter bestimmten Voraussetzungen in der Datenbank ausgelöst werden. Zum Beispiel wollen wir vielleicht beim Speichern von Eingaben vaildieren, ob sie korrekt sind. Oder wir wollen beim Löschen von Daten bestimmte weitere Daten löschen.

Trigger haben noch viele andere Anwendungsfälle und können Datenbankaktivitäten automatisieren, so dass wir nicht mehrere Queries manuell gegen die Datenbank schießen müssen. Für genauere Informationen ist in der „Nützliche Links“-Sektion ein Link zu Triggern beigefügt.

Aber ein Wort der Warnung: Bei Triggern müssen wir darauf achten, keine Trigger-Kaskaden zu bauen! Also ein Tigger löst den nächsten aus, der den nächsten auslöst, und so weiter … Dann verliert man schnell den Überblick und fragt sich, warum alles langsam wird.

Um einen Trigger zu nutzen, brauchen wir zwei Dinge:

Eine Funktion, die in unserem Fall NOTIFY aufruft und einen Trigger zurück gibt. Und einen Trigger, der nach jedem INSERT in unsere Blogpost-Tabelle diese Funktion aufruft. 

Unser Trigger soll eine Notification mit dem Topic blogpostAdded und der Blogpost id aussenden, die unser PostGraphile verarbeiten muss.

CREATE FUNCTION blogpost_added_subscription()
  RETURNS trigger
  LANGUAGE plpgsql
AS
$$
BEGIN
  IF tg_op = 'INSERT' THEN
    PERFORM pg_notify(
        'blogpostAdded',
        json_build_object(
            'event', 'newBlogpostAdded',
            'subject', new.id
        )::text
    );
  END IF;
END;
$$;
 
CREATE TRIGGER notifiy_after_blogpost_insert
  AFTER INSERT
  ON blogpost
  FOR EACH ROW
  EXECUTE PROCEDURE blogpost_added_subscription()
;

PostGraphile Plug-in

Im vierten Teil unserer Serie haben wir uns angeschaut, wie man PostGraphile mit Plug-ins erweitert. Dieses Wissen können wir jetzt gleich anwenden.

Unser Plug-in braucht eine TypeDef, in der der Subscription-Typ erweitert wird. Außerdem müssen wir den Typ unserer Event-Payload definieren.

type BlogpostSubscriptionPayload {
    blogpost: Blogpost
    event: String
}
 
extend type Subscription {
    newBlogpostAdded: BlogpostSubscriptionPayload @pgSubscription(topic: 'blogpostAdded')
}

Außerdem müssen wir im Resolver unseres Plug-ins beschreiben, was mit dem Datenbank-Event passiert. Wir bekommen ja von unserem Event nur die Blogpost-id. Weiterarbeiten wollen wir aber mit allen Informationen, die wir zu dem Blogpost haben. Der Resolver soll also, wenn er eine BlogpostSubscriptionPayload erhält, diese um die entsprechenden Daten anreichern.

resolvers: {    
    BlogpostSubscriptionPayload: {
        async blogpost(
            event,
            _args,
            _context,
            { graphile: { selectGraphQLResultFromTable } }
        ) {
            const rows = await selectGraphQLResultFromTable(
                sql.fragment`Blogpost`,
                (tableAlias, sqlBuilder) => {
                    sqlBuilder.where(
                        sql.fragment`${tableAlias}.id = ${sql.value(event.subject)}`
                    );
                }
            );
            return rows[0];
        },
    },
},

Wenn wir jetzt das Plug-in und die Subscriptions in PostGraphile aktivieren, können wir in unser GraphiQL im Browser wechseln und das schon einmal ausprobieren.

GraphiQL

In GraphiQL müssen wir zwei Tabs aufmachen: Im Ersten werden wir auf unsere Subscription lauschen, im Zweiten werden wir sie auslösen.

Im ersten Tab brauchen wir also folgenden Aufruf:

subscription BlogpostSubscription {
  newBlogpostAdded {
    blogpost
    event
  }
}

Das reicht schon, um zu lauschen. So lange kein Event reinkommt, gibt diese Subscription eine Warte-Info zurück.

Im zweiten Tab wollen wir die Subscription triggern, also müssen wir einen neuen Blogpost anlegen:

mutation MyMutation {
  createBlogpost(
    input: {blogpost: {title: "Toller Blogpost!", content: "10 Informationen zu Subscriptions. Nummer 11 wird euch vom Stuhl hauen!", blogpostType: TECHNICAL}}
  )
}

… und jetzt können wir im ersten Tab sehen, wie sich die Antwort unserer Subscription verändert!

Wenn man nur ausprobieren will …

Wer nur ausprobieren möchte wie das mit den Subscriptions funktioniert – ohne Plug-ins und Datenbank-Trigger zu schreiben –, kann die schon erwähnte --simple-subscriptions Flag nutzen. Die offizielle Dokumentation zu Subscriptions erklärt, wie das geht.

Für neue Blogupdates anmelden:


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.