PostGraphile Subscriptions

Avatar von Maria Haubner

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.

Goodies von Mayflower

Keine Sorge – Hilfe ist nah! Melde Dich unverbindlich bei uns und wir schauen uns gemeinsam an, ob und wie wir Dich unterstützen können.

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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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()
;
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() ;
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
type BlogpostSubscriptionPayload {
blogpost: Blogpost
event: String
}
extend type Subscription {
newBlogpostAdded: BlogpostSubscriptionPayload @pgSubscription(topic: 'blogpostAdded')
}
type BlogpostSubscriptionPayload { blogpost: Blogpost event: String } extend type Subscription { newBlogpostAdded: BlogpostSubscriptionPayload @pgSubscription(topic: 'blogpostAdded') }
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
BlogpostSubscriptionPayload erhält, diese um die entsprechenden Daten anreichern.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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];
},
},
},
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]; }, }, },
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
subscription BlogpostSubscription {
newBlogpostAdded {
blogpost
event
}
}
subscription BlogpostSubscription { newBlogpostAdded { blogpost event } }
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mutation MyMutation {
createBlogpost(
input: {blogpost: {title: "Toller Blogpost!", content: "10 Informationen zu Subscriptions. Nummer 11 wird euch vom Stuhl hauen!", blogpostType: TECHNICAL}}
)
}
mutation MyMutation { createBlogpost( input: {blogpost: {title: "Toller Blogpost!", content: "10 Informationen zu Subscriptions. Nummer 11 wird euch vom Stuhl hauen!", blogpostType: TECHNICAL}} ) }
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.

Goodies von Mayflower

Keine Sorge – Hilfe ist nah! Melde Dich unverbindlich bei uns und wir schauen uns gemeinsam an, ob und wie wir Dich unterstützen können.

Unsere Data-Webinar-Reihe

Avatar von Maria Haubner

Kommentare

Schreibe einen Kommentar

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

Für das Handling unseres Newsletters nutzen wir den Dienst HubSpot. Mehr Informationen, insbesondere auch zu Deinem Widerrufsrecht, kannst Du jederzeit unserer Datenschutzerklärung entnehmen.