knitr::opts_chunk$set(collapse = TRUE, comment = "#>")

Vorwort

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.

Installation

# 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")

Basics

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.

Anwendung

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.

Eigenschaften

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).

Die Verbindung

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.

Abfragen

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.

Ergebniss einer Abfrage

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

Verhalten der Verbindung bei einer Abfrage

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.

Testlauf

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.

Geschäftslogik

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.

Errorhandling

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.

Objektrelationale Abbildung

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]])

Fazit

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?



ChrisNice89/rdao documentation built on Aug. 26, 2022, 10:14 a.m.