knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
Da ich am 01.10 eine neue Stelle als Java Entwickler bei einer Bank angetreten habe und mich beruflich mit Datenbank Anwendungen und ORM (englisch object-relational mapping) beschäftige, habe ich das Projekt genutzt um mich dieser Thematik in R anzunehmen. Neben der reinen (Objektorientierten) Programmierung stand dabei die Softwarepaketierung im Fokus. Dafür habe ich das Projekt als eigenständiges Paket umgesetzt und für die weitere Distribution auf Github hochgeladen.
Die kommentierung von Code habe ich aus Übersichtsgründen weggelassen und dafür mehr Aufwand in die Benennung von (sprechenden) Variablen und grundlegender Paketarchitektur aufgebracht. Des Weiteren kann mit ?rdao::DieKlasse die Hilfe aufgerufen werden. Zeitlich war es mir leider nicht möglich, die Hilfe der einzelnen Klassen inhaltlich zu füllen.
Aus technischen Gründen musste ich während der Entwicklung von einer Access Datenbank abweichen. Alternativ habe ich daher aus dem Diamond Datensatz eine .db Datei erstellt und diese unter "rdao/db files external/Diamonds.db" auf Github abgelegt. Der originale Datensatz kann dem Paket nach dem Laden mit rdao::diamonds (siehe ?rdao::diamonds) entnommen werden.
# helper installer <- function(pkg){ new.pkg <- pkg[!(pkg %in% installed.packages()[, "Package"])] if (length(new.pkg)) install.packages(new.pkg, dependencies = TRUE) sapply(pkg, require, character.only = TRUE) } packages<- c("devtools","R6","DBI") installer(packages) devtools::install_github("Chrisnice89/rdao")
Laden des Pakets
library(rdao)
Für den Testlauf des Paketes den Pfad zur db (Gitub-Ordner: "db files external/Diamonds.db") in einer Variablen ablegen:
path<-"/Users/cnitz/Dev/R/rdao/db files external/Diamonds.db"
Um eine Klasse aus dem Paket verwenden zu können, muss eine 'factory()' Funktion aufgerufen werden. Unter einer Klasse (auch Objekttyp genannt) versteht man in der objektorientierten Programmierung ein abstraktes Modell bzw. einen Bauplan für eine Reihe von ähnlichen Objekten:
f <- connectionFactory() class(f)
Die connectionFactory()
Funktion instanziert intern ein Objekt der Klasse 'SqlFactory'.
Sobald die factory geladen wurde, kann diese jederzeit wieder verwendet werden um ein korrespondierendes Objekt zu erstellen.
Eigenschaften und Methoden der instanzierten Objekte können mit $
aufgerufen werden:
#Für ein db File builder.dbFile<-f$dbFile(path) class(builder.dbFile) #Für eine MS Access Db builder.Access<-f$msAccess("/file doesnt exisist") #Für MySql / Nicht implementiert builder.mySql<-f$mySql(database = "test_db",uid = "mySql",pwd = "password",host = "localhost",port = 5432)
Methoden und Felder (properties):
#Methode ohne Parameterübergabe builder.dbFile$addCredentials() #Methode mit Parameterübergabe builder.dbFile$addCredentials(username = "Admin",password = "SesameOpen") #Zugriff auf Felder ohne abschließende '()' builder.dbFile$path #Zuweisung eines Wertes zu einem Feld builder.dbFile$path<-"new path" builder.dbFile$path builder.dbFile$path<-path #Readonly Feld - Wert des Feldes kann nicht geändert werden #Error! builder.dbFile$builderProvider #builder.dbFile$builderProvider<-"msAccess" #Fehler in builder.dbFile$builderProvider <- "msAccess" : #kann den Wert einer festgestellten Bindung für 'builderProvider' nicht ändern
Methoden Verkettung:
class(builder.dbFile$credentials) builder.dbFile$credentials$username
Unter Method chaining (engl. chaining = Verkettung) wird in der Welt der objektorientierten Programmierung (OOP) eine spezielle Syntax verstanden, die das Ausführen einer Reihe von Methoden eines Objektes beschreibt.
Data Access Object (DAO, englisch für Datenzugriffsobjekt) ist ein Entwurfsmuster, das den Zugriff auf unterschiedliche Arten von Datenquellen (z. B. Datenbanken, Dateisystem) so kapselt, dass die angesprochene Datenquelle ausgetauscht werden kann, ohne dass der aufrufende Code geändert werden muss. Dadurch soll die eigentliche Programmlogik von technischen Details der Datenspeicherung befreit werden und flexibler einsetzbar sein. DAO ist also ein Muster für die Gestaltung von Programmierschnittstellen (APIs).
Die konkrete Implementierung der Objekte wurde mit Hilfe von R6 Klassen umgesetzt. Aus programmatischen Gründen wurde dabei gänzlich auf aktive Bindungen innerhalb der Klassen verzichtet. Des Weiteren wird im Kern für die Kommunikation zwischen Datenbank(en) und der Anwednung auf das DBI Interface zurückgegriffen.
Die Methoden der SqlFactory
instanzieren intern ein Objekt vom Typ 'Builder'.
Der Erbauer (englisch builder) ist ein Entwurfsmuster (buildern pattern) aus dem Bereich der Softwareentwicklung. Es gehört zur Kategorie der Erzeugungsmuster und trennt die Konstruktion komplexer Objekte von deren Repräsentationen, wodurch dieselben Konstruktionsprozesse wiederverwendet werden können.
Der Einsatz des Erbauer-Entwurfsmusters bietet sich an, wenn
1 Zu einem komplexen Objekt unterschiedliche Repräsentationen existieren sollen
2 Die Konstruktion eines komplexen Objekts unabhängig von der Erzeugung der Bestandteile sein soll
3 Der Konstruktionsablauf einen internen Zustand erfordert, der vor einem Klienten verborgen werden soll
Erstellen einer Verbindung mitbuilder$build()
connection.dbFile<-builder.dbFile$build() class(connection.dbFile) #connection.Accees<-builder.Access$build() #Fehler: <Builder> #Proc: <build()> #Beschreibung: <Ungültiger Pfad "/file doesnt exisist"> #connection.dbFile<-builder.mySql$build() #Fehler: <Builder> #Proc: <build()> #Beschreibung: <Noch nicht implementiert: mySql>
Der Konstruktionsprozess wird an einer dedizierten Stelle (im Builder
) gesteuert;
spätere Änderungen – etwa ein Mehrphasen-Konstruktionsprozess statt einer Einphasen-Konstruktion – lassen sich ohne Änderung der Klienten realisieren.
Es besteht eine enge Kopplung zwischen Produkt, konkretem Erbauer und den am Konstruktionsprozess beteiligten Klassen (hier die SqlFactory
als Direktor).
connection.dbFile$isConnected() connection.dbFile$connect() connection.dbFile$isConnected() connection.dbFile$disconnect() connection.dbFile$isConnected()
Die SqlConnection Klasse verwendet in seinem Nucleus das R Paket DBI. Im Gegensatz zu diesem erzeugt der Builder aber per default eine geschlossene Verbindung.
Diese kann explizit per connect()
geöffnet oder disconnect()
geschlossen werden. Weitere Details zum Verhalten der Connection Klasse werden im nächsten Abschnitt näher erläutert.
Die Methoden zum steuern der Verbindung geben jeweils einen boolean Wert als Indikator zurück.
query<-connection.dbFile$createQuery(sql = "SELECT * FROM diamonds") class(query) query$provider query$sql #query$sql<-"SELECT * FROM diamonds" #Fehler in query$sql <- "SELECT * FROM emeralds" : #kann den Wert einer festgestellten Bindung für 'sql' nicht ändern
Mit createQuery()
instanziiert die SqlConnection
ein SqlCommand. Die Klasse dient als CRUD (Create, Read, Update, Delete) Interface.
Die aktuelle Implementierung kann SQL Statements nur als Konkatenation verarbeiten. Um SQL-Injection (dt. SQL-Einschleusung) zu unterbinden sowie die Flexibilität zu erhöhen, könnte in einem zukünftigen Release parametrisierte Abfragen implementiert werden.
Im Vergleich:
quality<-"Premium" #quality<-"Premium" query.premium<-connection.dbFile$createQuery(sql = "SELECT * FROM diamonds WHERE cut=?") #qquery.premium$addParameter(name="quality", value= quality) query.premium$sql query.premium<-connection.dbFile$createQuery(sql = paste("SELECT * FROM diamonds WHERE cut='",quality,"'",sep="")) query.premium$sql
Darauf aufbauend kann das SqlCommand dann um die Möglichkeit des Prepared Statements erweitert werden. Ein Prepared Statement ist eine sogenannte vorbereitete Anweisung für ein Datenbanksystem. Im Gegensatz zu gewöhnlichen Statements enthält es noch keine Parameterwerte. Stattdessen werden dem Datenbanksystem Platzhalter übergeben. Soll ein Statement mit unterschiedlichen Parametern mehrere Male (z. B. innerhalb einer Schleife) auf dem Datenbanksystem ausgeführt werden, können Prepared Statements einen Geschwindigkeitsvorteil bringen, da das Statement schon vorübersetzt im Datenbanksystem vorliegt und nur noch mit den neuen Parametern ausgeführt werden muss.
Mit fetch()
wird die Query ausgeführt und die SqlConnection
liefert über das SqlCommand
Interface ein Objekt der Klasse SqlResult
zurück.
Handelt es sich bei der Abfrage um eine SQL-Aktionsabfrage, bspw.ein "INSERT" oder "DELETE" wird die Query mit dem Befehl execute()
ausgeführt und als Ergebnis wird die Anzahl der vom SQL-Statement betroffenen Datensätze zurück geliefert.
result<-query$fetch() result$rows() class(result)
Mit data
kann direkt auf die Daten zugegriffen werden:
#(Achtung Feld Zugriff) head(result$data) #result$data<-NULL #Fehler in result$data <- NULL : #kann den Wert einer festgestellten Bindung für 'data' nicht ändern
connection.dbFile$isConnected() result.premium<-query.premium$fetch(disconnect = TRUE) connection.dbFile$isConnected() head(result.premium$data)
Die Verbindung wird nur für einen Task geöffnet und anschließend sofort wieder geschlossen. Der optionale Steuerungsparameter disconnect
ist per default auf TRUE gesetzt.
Objektorientierte Programmiersprachen (OOP) kapseln Daten und Verhalten in Objekten, hingegen legen relationale Datenbanken Daten in Tabellen ab. Die beiden Paradigmen sind grundlegend verschieden. So kapseln Objekte ihren Zustand und ihr Verhalten hinter einer Schnittstelle und haben eine eindeutige Identität.
Für den Testlauf wird der diamonds Datensatz verwendet und beispielhaft eine einfache (fiktive) Geschäftslogik bestehend aus einer Tabelle sowie nur lesenden Aufgaben angenommen.
Im Testlauf soll exemplarisch dargestellt werden, welche Möglichkeiten R im Bereich der Objektorientierten Programmierung bietet und wie eine größere Datenbank Anwendung strukturiert nach den Grundsätzen der Objektorientierung implementiert werden könnte.
business<-diamondsFactory(connection.dbFile) class(business) diamonds.best<-business$loadBestQuality() head(diamonds.best$data) diamonds.selected<-business$loadColumns("carat","color") head(diamonds.selected$data)
Innerhalb der Businesslogik werden die relationen Zusammenhänge der zugrundeligenden Datenbank implementiert. Die Businesslogik kann um komplexe SQL-Abfragen über meherere Tabellen sowie weiterverarbeitende, Clientseitige Dunkelverabreitung erweitert werden. Beispiel: Kreditdaten kommen aus der Datenbank in der Businesslogik an, fließen in der Businesslogik in ein Prognosemodell und werden dann erst ans Frontend geliefert.
diamonds.lowbudget<-business$loadQuality("Poor","Fair", "Good") diamonds.lowbudget<-business$loadQuality("Fair", "Good") head(diamonds.lowbudget$data)
Das Errorhandling wird ebenfalls in der Businesslogik abgebildet.
Des Weiteren kann mit Hilfe von Objekt-orientierter Programmierung und dem rdao
framework eine Objektrelationale Abbildung dagestellt werden.
Eine Objektrelationale Abbildung (englisch object-relational mapping, ORM) ist eine Technik der Softwareentwicklung, mit der ein in einer objektorientierten Programmiersprache geschriebenes Anwendungsprogramm seine Objekte in einer relationalen Datenbank ablegen kann. Dem Programm erscheint die Datenbank dann als objektorientierte Datenbank, was die Programmierung erleichtert.
Im einfachsten Fall werden Klassen auf Tabellen abgebildet, jedes Objekt entspricht einer Tabellenzeile und für jedes Attribut wird eine Tabellenspalte reserviert. Die Identität eines Objekts entspricht dem Primärschlüssel der Tabelle:
diamonds<-business$loadDiamonds() class(diamonds) class(diamonds[[1]])
Mit dem DBI- und dem R6 Paket existieren in R mächtige Werkzeuge zum entwickeln von Objektorientierten Datenbanklösungen.
Die Anwendung kann als Blaupause für weitere Implementierungen dienen und vermittelt einen ersten Eindruck wie eine Objektorientierte Programmstruktur in R aussehen könnte.
Aufgrund der Komplexität des Themas und der im Rahmen einer Projektarbeit bemessenen Zeit verbleiben viele offene Fragen:
Wie könnten tatsächliche Interface implementiert werden? Wie schreibt die Anwendung das gemappte Objekt (effizient) zurück in die Datenbank? Wie könnte das SqlResult noch weiter gekapselt werden?
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.