– Gastbeitrag von Michael Schakulat –
Die Facebook Graph API gestattet uns Zugriff auf die Daten aller Open Graph Objekte, sofern wir autorisiert sind, sie abzufragen. Haben wir eine solche Autorisierung erhalten, ist es mithilfe der API und der von Facebook bereitgestellten SDKs ein leichtes, an die benötigten Daten zu kommen. Und geht es dabei nur um ein paar Benutzerinformationen, wie z.B. Benutzername, Profilbild und Geburtsdatum, brauchen wir uns auch um Performance nur wenig Gedanken zu machen.
Viele Facebook Anwendungen sind allerdings wesentlich komplexer und benötigen an sehr vielen Stellen Zugriff auf die Graph API. Wer oft mit der Graph API arbeitet, merkt hier schnell, dass die Performance bei mehreren Abfragen gegen die API, sehr schnell in die Knie geht. Natürlich ist das nicht weiter verwunderlich, denn für jede Abfrage wird ein Request gestartet, der auf eine Antwort der Facebook Server wartet und sie letztendlich entgegen nimmt. Das kostet Zeit und ist dazu noch eine Performancebremse, gegen die man selbst nicht sehr viel machen kann.
Um diese Performanceprobleme in den Griff zu kriegen, hat Facebook vor Kurzem die Batch Requests eingeführt. Mit diesen Batch Requests ist es nun möglich, mehrere Abfragen zu einer einzigen zusammenzufassen. Der Entwickler kann nun also mehrere Abfragen gleichzeitig an die Facebook Server schicken, dort ausführen lassen und die Antwort dann wiederum gebündelt erhalten. Das spart Zeit und Ressourcen, zumindest auf Entwicklerseite und darauf kommt es uns ja schließlich an.
Was alles möglich ist
Zum einen bieten uns die Batch Requests die Möglichkeit, bis zu 20 Requests in einer Abfrage zu bündeln und anschließend vollkommen unabhängig voneinander parallel von Facebook ausführen zu lassen.
Eine weitere, sehr interessante Möglichkeit, liegt darin, die gebündelten Abfragen in Abhängigkeit zueinander zu stellen. So könnte man z.B. eine Abfrage auf dem Ergebnis einer anderen Abfrage basieren lassen. Beispiel: Abfrage 1 fragt die IDs von 5 Freunden ab und Abfrage 2 fragt die Details zu diesen 5 Freunden ab. Beide Abfragen (und sogar noch 18 weitere) kann man mit nur einem Request abschicken.
Es ist außerdem möglich FQL Queries und FQL Multiqueries in einem Batch Request zu nutzen oder auch Daten zu veröffentlichen.Wie setze ich einen Batch Request ab?Im Prinzip funktioniert es so, wie jede andere Abfrage gegen die Graph API auch. Abgefragt wird also gegen die Basis URL https://graph.facebook.com.
Der eigentliche Request wird als JSON Objekt übergeben. Z.B.:
[{"method":"GET","relative_url":"me"}]
Mehrere Requests werden durch Kommata getrennt.
[{"method":"GET","relative_url":"me"},
{"method":"GET","relative_url":"me/friends?limit=50"}]
Der Request besteht immer aus einer Methode (GET, PUT, POST oder DELETE) und einer relativen URL (der Teil hinter https://graph.facebook.com) . Weiterhin können optional weitere Header (entsprechend der HTTP Header) und ein Body (bei PUT und POST Requests) angegeben werden. Ebenfalls erforderlich ist ein gültiger Access Token.
Beispiele
Ich werde alle Beispiele mit der Facebook PHP SDK durchführen und hier nur die relevanten Teile, also die Abfragen gegen die API zeigen. Die kompletten Beispiele sind aus meinen SVN- bzw. github Repositorys auszuchecken.
Um voneinander unabhängige Abfragen simultan durchführen zu lassen, werden wir uns fünf große Seiten raus suchen und deren Newsfeed abfragen. Hier sieht man sehr gut, wie einem die Batch Requests helfen können, Zeit zu sparen.
$facebook->api('adidas/feed?limit=100');
$facebook->api('levis/feed?limit=100');
$facebook->api('marketingde/feed?limit=100');
$facebook->api('starbucks/feed?limit=100');
$facebook->api('cocacola/feed?limit=100');
Wie man sieht, werden hier die Newsfeed von fünf großen Facebook Seiten abgefragt. Es werden jeweils die ersten 100 Einträge abgefragt. Diese Abfrage dauerte bei mir 17,6 Sekunden.
Die selben Daten fragen wir nun mittels Batch Request ab.
$facebook->api('?batch=[' .
'{"method":"GET","relative_url":"adidas/feed?limit=100"},' .
'{"method":"GET","relative_url":"levis/feed?limit=100"},' .
'{"method":"GET","relative_url":"marketingde/feed?limit=100"},' .
'{"method":"GET","relative_url":"starbucks/feed?limit=100"},' .
'{"method":"GET","relative_url":"cocacola/feed?limit=100"}]',
'post',
array('access_token' => $facebook->getAccessToken()));
Diese Abfrage dauerte bei mir 7,81 Sekunden.
Man sieht also, dass die Zeitersparnis enorm ist. Da alle Abfragen parallel ausgeführt werden, dauert die Antwort nur so lange, wie die längste Einzelabfrage. Fragt man also die Daten eines Benutzers ab, z.B. Name, Geburtstag, Lieblingsfilme, Likes, Checkins etc., wird aus Performance Sicht bei den Batch Requests kaum auffallen, dass der Request aus mehreren Einzelabfragen besteht.
Als weiteres Beispiel möchte ich hier das Abfragen von voneinander abhängigen Daten zeigen. Dazu starten wir einen Batch Request, der aus zwei Abfragen besteht. Die erste Abfrage liest die IDs von fünf beliebigen Freunden aus. Die zweite Abfrage holt dann Informationen über diese fünf Freunde ein.
$result = $facebook->api('?batch=[' .
'{"method":"GET","name":"first","relative_url":"me/friends?limit=5"},' .
'{"method":"GET","relative_url":"?ids={result=first:$..id}"}]',
'post',
array('access_token' => $facebook->getAccessToken()));
print_r ($result);
Hinweis
Um Daten in einem JSON Objekt zu referenzieren, nutzt Facebook JSONPath. Die Syntax eines JSONPath Ausdrucks sieht wie folgt aus:
{result=(name_der_übergeordneten_abfrage):(JSONPath Ausdruck)}. Der Ausdruck first:$..id sagt aus: Hole das id Attribut von allen Ergebnissen der Abfrage first. Möchte man ein bestimmtes Ergebnis ansprechen, kann man folgende Schreibweise verwenden first:$.[0].id. Damit würde man auf das erste Ergebnis referenzieren können. Wer mehr darüber erfahren will, erhält unter http://goessner.net/articles/JsonPath/ weitere Informationen.
Der Ablauf eines solchen Requests sieht wie folgt aus:
- Der Request wird abgeschickt und analysiert.
- Es sind voneinander abhängige Abfragen enthalten, also wird zuerst die Elternabfrage ausgeführt.
- Ist die Elternabfrage fehlerhaft, werden die davon Abhängigen Abfragen nicht ausgeführt.
- Ist die Elternabfrage OK, werden die davon abhängigen Abfragen ausgeführt.
- Als Antwort wird standardmäßig nur die Ausgabe der Kindabfrage geschickt. Möchte man auch die Daten der Elternabfrage in der Antwort erhalten, kann man das mit dem zusätzlichen Parameter „omit_response_on_success“: false, in der Elternabfrage, forcieren.Die Graph API ermittelt selbst, ob es Abhängigkeiten zwischen den einzelnen Abfragen eines Batch Requests gibt. Man kann allerdings, mittels des Parameters depends_on, auch selbst die Abhängigkeit festlegen.
[{"method":"GET","relative_url":"me","name":"first"},{"method":"GET","relative_url":"me/friends","depends_on":"first"}]
Wie eingangs erwähnt, ist es auch möglich FQL Queries in Batch Requests zu nutzen. Als Beispiel lassen wir uns den Namen des Benutzers mit der ID 4 (Mark Zuckerberg) ausgeben.
$result = $facebook->api('?batch=[' .
'{"method":"POST",' .
'"relative_url":"method/fql.query?query=select+name+from+user+where+uid=4"}]','post', array('access_token' => $facebook->getAccessToken()));
print_r ($result);
Ebenfalls schon erwähnt habe ich die Möglichkeit, Daten per Batch Request zu veröffentlichen oder mit einer Abfrage zu mischen. Als Beispiel werden wir den Namen eines Freundes abrufen und anschließend eine Statusnachricht, die den Namen enthält, veröffentlichen.
$result = $facebook->api('?batch=[' .
'{"method":"GET",' .
'"name":"first",' .
'"relative_url":"me/friends?limit=1"},' .
'{"method":"POST",' .
'"relative_url":"me/feed",' .
'"body":"message={result=first:$.data..name} ist mein Freund."}]',
'post', array('access_token' => $facebook->getAccessToken()));
print_r ($result);
Hinweis
Das Veröffentlichen von Statusnachrichten mittels Batch Requests ist zurzeit noch Fehlerhaft. Facebook quittiert den Versuch, eine Feed Story zu veröffentlichen, mit einem Fehler. Ich habe dazu einen Bugreport erstellt, der bereits geprüft wird.
Eine weitere, sehr interessante Sache ist die Tatsache, dass die einzelnen Abfragen eines Batch Requests unterschiedliche Access Token enthalten können. Das ist z.B. dann sinnvoll, wenn mehrere Benutzer gleichzeitig abgefragt werden sollen.
Fazit
Die Batch Requests gehören für mich zu den sinnvollsten Erweiterungen, die Facebook in letzter Zeit für Entwickler bereitgestellt hat. Gerade bei größeren Anwendungen merkt man immer wieder, dass die Verbindungen zum Facebook Server oftmals der Flaschenhals sind. Die Batch Requests werden mit Sicherheit sehr großen Anklang finden.
Hinweis
Du kannst alle Codebeispiele über ein SVN Repository auschecken. Dieses Beispiel findest du unter folgender Adresse: http://svn.abouttheweb.de/atwtutorials/trunk/batch-requests/
Alternativ kannst Du alle Codebeispiele über das AboutTheWeb github Repository unter https://github.com/AboutTheWeb/AboutTheWeb auschecken.
Michael Schakulat arbeitet zurzeit als PHP- und Datenbankentwickler mit den Schwerpunkten der Entwicklung von Drupal gestützten Webseiten und der Facebook Anwendungsentwicklung. Nebenbei betreibt er unter http://www.abouttheweb.de einen Blog, in dem er überwiegend Themen zur Entwicklung von Facebook Anwendungen aufgreift.
Ich bin definitiv für mehr Programmierthemen auf eurem Blog! Interessanter Beitrag
Beispiel oder vielleicht hat auch jemand ein Tipp, wie bekommt man die Social Plugins in die iFrame Tabs bei mir Haut das nicht hin wenn ich das mache wie es in den developer Foren steht!
Ja
Ja, kleine Tutorials zu „Mini-Apps“ oder dergleichen wären super…
wäre sehr interessant!
Ja gerne!
Ja bitte!!!
Auf jeden Fall
Danke für die Tipps, werde ich gleich mal ausprobieren.
Auf jeden Fall !!!
Netter Artikel. Großen Dank dafür. Aber kann es sein, daß der Batch-Request noch ein wenig Buggy ist, da ich in 3 von 4 Fällen immer eine cURL-Exception 52 Empty Reply bekomme.
Mark, jop muss noch ganz schon verbuggt sein.