Wie man REST-APIs mit R Plumber erstellt


Datenoperationen sind ein zunehmend wichtiger Bestandteil der Datenwissenschaft, da sie Unternehmen ermöglichen, große Geschäftsdaten effektiv zurück in die Produktion zu führen. Bei STATWORX operationalisieren wir daher unsere Modelle und Algorithmen, indem wir sie in Application Programming Interfaces (APIs) übersetzen. Representational State Transfer (REST) APIs eignen sich gut, um als Teil einer modernen Microservices-Infrastruktur implementiert zu werden. Sie sind flexibel, einfach bereitzustellen, zu skalieren und zu warten, und sie sind zudem von mehreren Clients und Client-Typen gleichzeitig zugänglich. Ihr Hauptzweck besteht darin, das Programmieren zu vereinfachen, indem sie die zugrunde liegende Implementierung abstrahieren und nur Objekte und Aktionen bereitstellen, die für jede weitere Entwicklung und Interaktion benötigt werden.
Ein zusätzlicher Vorteil von APIs ist, dass sie eine einfache Kombination von Code ermöglichen, der in verschiedenen Programmiersprachen oder von unterschiedlichen Entwicklerteams geschrieben wurde. Dies liegt daran, dass APIs von Natur aus voneinander getrennt sind und die Kommunikation mit und zwischen APIs über IP oder URL (http) erfolgt, typischerweise im JSON- oder XML-Format. Stellen Sie sich beispielsweise eine Infrastruktur vor, in der eine API, die in Python geschrieben ist, und eine, die in R geschrieben ist, miteinander kommunizieren und eine Anwendung bedienen, die in JavaScript geschrieben ist.
In diesem Blogbeitrag werde ich Ihnen zeigen, wie Sie ein einfaches R-Skript, das Tabellen vom Wide-Format ins Long-Format transformiert, mit dem R-Paket Plumber in eine REST-API übersetzen und wie Sie es lokal oder mit Docker ausführen können. Ich habe diese Beispiel-API für unser Trainee-Programm erstellt, und sie dient unseren neuen Datenwissenschaftlern und Ingenieuren als Ausgangspunkt, um sich mit dem Thema vertraut zu machen.
Übersetze das R Script
Das Umwandeln eines R-Skripts in eine REST-API ist ziemlich einfach. Alles, was Sie zusätzlich zu R und RStudio benötigen, ist das Paket Plumber und optional Docker. Mit REST-APIs kann durch das Senden einer REST-Anfrage interagiert werden, und die wahrscheinlich am häufigsten verwendeten Anfragen sind GET, PUT, POST und DELETE. Hier ist der Code der Beispiel-API, die Tabellen vom Wide-Format ins Long-Format oder vom Long-Format ins Wide-Format transformiert:
## transform wide to long and long to wide format
#' @post /widelong
#' @get /widelong
function(req) {
# library
require(tidyr)
require(dplyr)
require(magrittr)
require(httr)
require(jsonlite)
# post body
body <- jsonlite::fromJSON(req(dollar sign)postBody)
.data <- body(dollar sign).data
.trans <- body(dollar sign).trans
.key <- body(dollar sign).key
.value <- body(dollar sign).value
.select <- body(dollar sign).select
# wide or long transformation
if(.trans == 'l' || .trans == 'long') {
.data %<>% gather(key = !!.key, value = !!.value, !!.select)
return(.data)
} else if(.trans == 'w' || .trans == 'wide') {
.data %<>% spread(key = !!.key, value = !!.value)
return(.data)
} else {
print('Please specify the transformation')
}
}
Wie Sie sehen können, handelt es sich um eine Standard-R-Funktion, die durch spezielle Plumber-Kommentare @post und @get erweitert wird, die es der API ermöglichen, auf diese Arten von Anfragen zu reagieren. Es ist notwendig, den Pfad /widelong zu jeder eingehenden Anfrage hinzuzufügen. Dies geschieht, weil es möglich ist, mehrere API-Funktionen zu stapeln, die auf verschiedene Pfade reagieren. Wir könnten beispielsweise eine weitere Funktion mit dem Pfad /naremove zu unserer API hinzufügen, die NAs aus Tabellen entfernt.
Die R-Funktion selbst hat ein Funktionsargument req, das verwendet wird, um einen (POST) Request Body zu empfangen. Im Allgemeinen gibt es zwei verschiedene Möglichkeiten, zusätzliche Argumente und Objekte an eine REST-API zu senden: den Header und den Body. Ich habe mich entschieden, nur einen Body und keinen Header zu verwenden, was die API sauberer und sicherer macht und es uns ermöglicht, größere Objekte zu senden. Ein Header könnte beispielsweise verwendet werden, um einige optionale Funktionsargumente festzulegen, sollte jedoch ansonsten sparsam verwendet werden.
Die Verwendung eines Bodys mit der API ist auch der Grund, warum gleichzeitig GET- und POST-Anfragen (@post, @get) erlaubt sind. Während einige Clients es vorziehen, einen Body mit einer GET-Anfrage zu senden, wenn sie nicht dauerhaft etwas an den Server posten usw., haben viele andere Clients überhaupt nicht die Möglichkeit, einen Body mit einer GET-Anfrage zu senden. In diesem Fall ist es zwingend erforderlich, eine POST-Anfrage hinzuzufügen. Typische Clients sind Anwendungen, integrierte Entwicklungsumgebungen (IDEs) und andere APIs. Durch die Akzeptanz beider Anfragetypen gewinnt unsere API daher eine größere Antwortflexibilität.
Für das Anfrage-Antwort-Format der API habe ich mich entschieden, bei der JavaScript Object Notation (JSON) zu bleiben, die wahrscheinlich das häufigste Format ist. Es wäre möglich, stattdessen die Extensible Markup Language (XML) mit R Plumber zu verwenden. Die Entscheidung für das eine oder das andere wird höchstwahrscheinlich davon abhängen, welche zusätzlichen R-Pakete Sie verwenden möchten oder welches Format die Clients der API überwiegend verwenden. Die R-Pakete, die in meiner Beispiel-API verwendet werden, um REST-Anfragen zu bearbeiten, sind jsonlite und httr. Die drei Tidyverse-Pakete werden verwendet, um die Tabellenumwandlung in Breit- oder Langformat vorzunehmen.
API ausführen
Die fertige REST-API kann lokal mit R oder RStudio wie folgt ausgeführt werden:
library(plumber)
widelong_api <- plumber::plumb("./path/to/directory/widelongwide.R")
widelong_api(dollar sign)run(host = '127.0.0.1', port = 8000)
Beim Starten der API stellt das Plumber-Paket uns eine IP-Adresse und einen Port zur Verfügung, und ein Client, z.B. eine andere R-Instanz, kann nun beginnen, REST-Anfragen zu senden. Es öffnet auch ein Browser-Tool namens Swagger, das nützlich sein kann, um zu überprüfen, ob Ihre API wie beabsichtigt funktioniert. Sobald die Entwicklung einer API abgeschlossen ist, würde ich empfehlen, ein Docker-Image zu erstellen und es in einem Container auszuführen. Das macht die API sehr portabel und unabhängig von ihrem Host-System. Da wir die meisten APIs in der Produktion verwenden und sie z.B. auf einen Firmenserver oder in die Cloud bereitstellen möchten, ist dies besonders wichtig. Hier ist das Dockerfile, um das Docker-Image der Beispiel-API zu erstellen:
FROM trestletech/plumber
# Install dependencies
RUN apt-get update --allow-releaseinfo-change && apt-get install -y
liblapack-dev
libpq-dev
# Install R packages
RUN R -e "install.packages(c('tidyr', 'dplyr', 'magrittr', 'httr', 'jsonlite'),
repos = 'https://cran.us.r-project.org')"
# Add API
COPY ./path/to/directory/widelongwide.R /widelongwide.R
# Make port available
EXPOSE 8000
# Entrypoint
ENTRYPOINT ["R", "-e",
"widelong <- plumber::plumb('widelongwide.R');
widelong(dollar sign)run(host = '0.0.0.0', port= 8000)"]
CMD ["/widelongwide.R"]
Eine REST-Anfrage senden
Die Wide-Long-Beispiel-API kann im Allgemeinen auf jeden Client reagieren, der eine POST- oder GET-Anfrage mit einem Body im JSON-Format sendet, die eine Tabelle im CSV-Format und alle benötigten Informationen zur Transformation enthält. Hier ist ein Beispiel für eine Webanwendung, die ich für unser Trainee-Programm geschrieben habe, um die Wide-Long-API zu ergänzen:

Die Anwendung ist in R Shiny geschrieben, einem großartigen R-Paket, um Ihre statischen Diagramme und Ausgaben in ein interaktives Dashboard zu verwandeln. Wenn Sie daran interessiert sind, wie man Dashboards in R erstellt, schauen Sie sich andere Beiträge in unserem STATWORX Blog an.
Zu guter Letzt finden Sie hier ein Beispiel, wie man eine REST-Anfrage von R oder RStudio senden kann:
library(httr)
library(jsonlite)
options(stringsAsFactors = FALSE)
# url for local testing
url <- "https://127.0.0.1:8000"
# url for docker container
url <- "https://0.0.0.0:8000"
# read example stock data
.data <- read.csv('./path/to/data/stocks.csv')
# create example body
body <- list(
.data = .data,
.trans = "w",
.key = "stock",
.value = "price",
.select = c("X","Y","Z")
)
# set API path
path <- 'widelong'
# send POST Request to API
raw.result <- POST(url = url, path = path, body = body, encode = 'json')
# check status code
raw.result(dollar sign)status_code
# retrieve transformed example stock data
.t_data <- fromJSON(rawToChar(raw.result(dollar sign)content))
Wie Sie sehen können, ist es ziemlich einfach, REST-Anfragen in R zu stellen. Wenn Sie einige Testdaten benötigen, könnten Sie das Aktiendaten-Beispiel aus dem Tidyverse verwenden.
Zusammenfassung
In diesem Blogbeitrag habe ich Ihnen gezeigt, wie Sie ein einfaches R-Skript, das Tabellen vom Breit- ins Langformat transformiert, mit dem R-Paket Plumber in eine REST-API übersetzen und wie Sie es lokal oder mit Docker ausführen können. Ich hoffe, Sie haben die Lektüre genossen und etwas darüber gelernt, wie man R-Skripte mit dem R-Paket Plumber in REST-APIs operationalisiert und wie man sie lokal und mit Docker ausführt. Sie sind herzlich eingeladen, jeden Code aus diesem Blogbeitrag zu kopieren und zu verwenden, um Ihre REST-APIs mit R zu starten und zu erstellen.
Bis dahin bleiben Sie dran und besuchen Sie bald wieder unseren STATWORX Blog.
Wir stellen ein!
Data Engineering ist Ihr Ding und Sie suchen einen Job? Wir suchen derzeit Junior Consultants und Consultants im Bereich Data Engineering. Überprüfen Sie die Anforderungen und Vorteile der Zusammenarbeit mit uns auf unserer Karriere-Seite. Wir freuen uns auf Ihre Bewerbung!