Proof of concept für ein alternatives Build-System für Tntnet

Standard

Ich habe in letzter Zeit viel darüber nachgedacht, wie man Tntnet attraktiver machen kann. Angeregt von dem Buch „Open Source Projektmanagement. Softwareentwicklung von der Idee zur Marktreife“ (Von Michael Prokop, Open Source Press; Auflage: 1. Aufl. 27. September 2010, ISBN-10: 3937514600), habe ich mir deshalb Gedanken über die Zielgruppen gemacht.

Herausforderung

Ich sehe für Tntnet potenziell User, die sich grob in zwei Gruppen teilen lassen. Die erste Gruppe besteht aus Usern, die schon sehr viel Erfahrung mit C++ haben und seit vielen Jahren damit arbeiten. Diese User haben vielleicht bisher einen weiten Bogen um Webprogrammierung gemacht, weil sie alles was sie bisher in diesem Bereich gesehen haben für grausame Pfuscherei hielten.

Die zweite Gruppe, die ich sehe, hat sehr viel Erfahrung mit den klassischen Vertretern der Webprogrammierung, wie PHP, Python, Ruby on Rails, Java und den ganzen Frameworks, die damit in Zusammenhang stehen. Diese User haben bisher um C++ einen weiten Bogen gemacht, weil sie glauben, die Sprache sei unelegant und würde die Produktivität hemmen.

Für die erste Gruppe hat Tntnet schon viel getan, um den Einstiegt attraktiv zu gestalten. Für die Entwickler, die aus der C++ Ecke kommen, dürfte Tntnet viele bekannte Konzepte aufweisen. Tntnet bietet die Freiheiten, die ein C++-Programmierer gewohnt ist. Tntnet macht nicht viele Vorschriften, wie ein Projekt umzusetzen ist. Durch Tntnet wird Webprogrammierung für C++-Entwickler abstrahiert und vereinfacht.

Bei der zweiten Gruppe mit Usern, die aus der Welt der hochabstrakten Skriptsprachen mit ihren mächtigen Frameworks kommen, sehe ich noch erhebliche Barrieren. Selbst Java-Entwickler, denen Compiler nicht gänzlich fremd sind, werden sich anfänglich mit dem Buildsystem von C++ schwer tun. Ich fürchte, dass Viele über diese Hürde nicht hinweg kommen.

Lösungsansatz

Diese Überlegung hat mich dazu angeregt, darüber nachzudenken, wie man das Buildsystem vereinfachen kann, um die Barriere für Umsteiger zu senken.

Ich habe mir GNU Make, Autotools, CMake und QMake anschaut. Das sind mächtige Werkzeuge, aber viel zu kompliziert und mit zu viel „Magie“, die irgendwas im Hintergrund macht, das man nicht versteht. Ich habe mir auch kurz das Buildsystem von Code::Blocks angesehen. Code::Blocks ist eine IDE für C++. Es gibt eine sehr schöne Doku darüber, wie man Tntnet mit Code::Blocks verwenden kann. Es gibt nur ein Haken: Man kann keine Tntnet-Projekt mit Code::Blocks übersetzen ohne X, also auch nicht skriptgesteuert durch ein Continuous Integration Server wie Jenkins.

Nachdem ich diese Möglichkeiten alle verworfen habe, bin ich zu dem Entschluss gekommen, etwas eigenes zu probieren und mit einem proof of concept zu beginnen. Zuerst versuchte ich ein Wrapper oder eine „Premake“ zu schreiben, der aus seiner vereinfachten yaml-Konfiguration ein Automake-File baut. Nach ein paar Versuchen stellte ich fest, dass der Weg zu kompliziert ist, da versucht wird, die verwirrende „Magie“ von Automake & Co durch noch mehr „Magie“ zu verstecken. Also habe ich probier ein Build-Tool zu schreiben, das ohne andere Tools auskommt, um die Komplexität zu reduzieren.

Umsetzung

Herausgekommen ist ein Tool, dem ich den Namen Tntmake gegeben habe. Der Code ist auf GitHub zu finden.

Das Tool ist in Ruby geschrieben. Ich habe Ruby nur gewählt, weil ich mich beruflich derzeit damit beschäftigen muss. Als Proof of concept ist das auch okay. Aber die richtige Umsetzung werde ich eher in einer anderen Sprache realisieren, da schon einige Probleme aufgetreten sind. Zum einen ist da die Abhängigkeit zu einer weiteren Programmiersprache, die standardmäßig nicht immer vorhanden ist. Das habe ich zum Beispiel auf Pidora gemerkt. Dann nervt mich die mangelnde Kompatibilität unter den Ruby-Versionen, da ich von C++ anderes gewohnt bin.

Überrascht war ich beim Thema Geschwindigkeit. Im Vergleich zu C++ kriecht Ruby, wie zu erwarten, nur so dahin. Verglichen mit Autotools hingegen, ist mein Proof of concept ohne Optimierungen doppelt so schnell wie Automake. Damit habe ich nicht gerechnet. Um mein eigenes Projekt Peruschim zu übersetzen, brauchte Autotools ca. 2 Min. 11 Sek, Tntmake hingegen nur 53 Sek.

Geschwindigkeit ist aber nicht das vorrangige Ziel von Tntmake. Es soll auch nicht das Bessere Automake sein oder CMake, QMake und Co ersetzen. Ziel ist allein Tntnet-Projekte möglichst einfach zu übersetzen. Ich denke, Umsteiger,  die vom Skriptsprachenuniversum kommen, interessiert das Buildsystem erst mal nicht, solange es ihnen nicht im Weg steht. Für große Projekte mit besonderen Anforderungen wird der Ansatz von Tntmake nicht reichen. Diese Projekte realisieren aber in aller Regel Fachleute, die nichts anderes tun haben, als sich um den Build-Prozess zu kümmern und für die dann Tools wie CMake, Autotools und Co kein Hindernis darstellen.

Wie geht es weiter?

Während der Arbeit an Tntmake habe ich das Konfigurationsformat von yaml auf json umgestellt. tntnet unterstützt yaml (noch) nicht. Ich werde im nächsten Schritt probieren, ob es machbar ist, mit Tntnet einen webbasierten Projekt-Wizard zu schreiben, der Tntmake als Backent verwendet. Deshalb war es mir wichtig, das die Konfiguration (gut) Maschinenlesbar ist.

Ursprünglich wollte ich Tntmake multithreading-fähig machen. Ich habe das bereits im Code umgesetzt. Mit dem Flag „tntmake -tb“ kann man das auch ausprobieren. Ich dachte, dass ich das bräuchte, weil Ruby so langsam ist. Aus irgendwelchen Gründen funktioniert Multithreading aber leider nicht. Da ich festgestellt habe, dass Tntmake derzeit schnell genug ist, verwende ich darauf zunächst keine weitere Zeit.

Ich werde in der nächsten Zeit den Code, in Vorbereitung auf eine Reimplementierung in eine andere Programmiersprache, noch etwas aufräumen.


Creative Commons Lizenzvertrag

Advertisements

MVC-Architektur in Tntnet (Variante II.)

Standard

Wie schon im Artikel „MVC-Architektur in Tntnet (Variante I.)“ angekündigt, kommt hier eine alternative Variante, um eine MVC-Architektur in Tntnet zu realisieren. Diese Variante ist etwas einfacher in der Handhabung. Dafür stellt sie eine nicht ganz 100%-ig saubere Lösung dar. Denn hier wird erst der View aufgerufen; dieser ruft den Controller auf, der wiederum mit dem Model arbeitet. Klassischer Weise wird erst der Conroller und dann der View aufgerufen. Ein weitere Punkt ist sowohl ein Vor- wie ein Nachteil. Der Aufruf des Controllers wird nicht über das Routing gesteuert, da der View den Controller aufruft. Der Vorteil ist, dass sich eine einzige Route mit der Komponente verbindet. Das macht es einfacher und transparenter über URLs Zugriffsrechte zu kontrollieren oder generische Komponenten-Link-URLs zu verwenden. Dazu zu einem späterem Zeitpunk mehr. Jetzt erst mal zur konkreten Umsetzung der alternativen MVC-Variante.

Contoller

Die Controller-Klasse ist eine ganz normale C++-Klasse. Diese Klasse wird von dem View, der in der ecpp-Auszeichnungssprache erstellt wird, eingebunden. Bevor gezeigt wird, wie der View die logische Verarbeitung der Benutzereingaben an den Controller delegiert, wird hier erst mal ein exemplarischer Controller-Code gezeigt. Der gezeigte Controller soll die Kontrolle über den Login-Prozess übernehmen: Die Datei AlterLogInController.h:

#ifndef ALTERLOGINCONTROLLER_H
#define ALTERLOGINCONTROLLER_H

#include <Core/models/UserSession.h>
#include <tnt/httprequest.h>
#include <tnt/httpreply.h>
#include <iostream>

    class AlterLogInController
    {
        public:
            AlterLogInController (UserSession& userSession_): userSession(userSession_){};
            void operator() (
                tnt::HttpRequest& request,
                tnt::HttpReply& reply,
                tnt::QueryParams& qparam
            );
            std::string feedback;
            UserSession& userSession;
    };
#endif

Und die Datei AlterLogInController.cpp:

#include <Core/controller/AlterLogInController.h>
#include <Core/manager/WebACL.h>
#include <Core/models/UserSession.h>
#include <cxxtools/log.h>
#include <tnt/httprequest.h>
#include <tnt/httpreply.h>

log_define("Core.AlterLogInController")

void AlterLogInController::operator() (
    tnt::HttpRequest& request,
    tnt::HttpReply& reply,
    tnt::QueryParams& qparam)
{

    // define the query parameters
    std::string  arg_name     = qparam.arg<std::string>("arg_name");
    std::string  arg_password = qparam.arg<std::string>("arg_password");
    bool  arg_login_button    =  qparam.arg<bool>("arg_login_button");

    log_debug("authUser(" << arg_name << ", ***)");

    if ( arg_login_button ) {
        if ( WebACL::authUser ( arg_name, arg_password ) )
        {
            userSession.setUserName ( arg_name );
            userSession.addRoll (  WebACL::getRoll ( arg_name ) );
            reply.redirect ( "/home" );
        }
        else
        {
            log_debug("fail");
            feedback = "Login fehlgeschlagen!";
        };
    }
}

Das einzig Besondere an der Klasse ist, dass sie eine Methode "operator()" aufweist, der bestimmte Referenzen übergeben werden. Das sind Objekte der Klassen tnt::HttpRequest, tnt::HttpReply und tnt::QueryParams die in der ecpp-View-Umgebung zur Verführung stehen. Dem Controller werden die Referenzen auf diese Instanzen übergeben, damit dieser mit den Werten und Informationen weiter arbeiten kann. So z.B. stecken in tnt::QueryParams& qparam die übergebenen http-request-Parameter mit ihren Werten. In unserem Fall ist das der Name, das Passwort und ob der Button,  für das Abschicken des Login-Formulars geklickt wurde. Die Art wie die Parameter aus den übergebenen Objekt-Reverenzen heraus gelesen werden, ist die selbe wie im Artikel „MVC-Architektur in Tntnet (Variante I.)“ beschrieben. Nur ein kleiner Hinweis auf die Besonderheit des Funktion-Aufrufes reply.redirect ( "/home" );. Diese Funktion der Klasse tnt::HttpReply wird vm Tntnet-Framework bereitgestellt und dient dazu, auf eine andere Seite weiter zu leiten. In unserem Fall auf die Startseite /home. Wird der Login abgelehnt,  so wird eine Nachricht in der Klasseneigenschaft feedback für den View (/home) bereitgehalten. Dies geschieht in der Zeile

feedback = "Login fehlgeschlagen!";

Model

Für das Model gilt in dieser Variante II das Gleiche, wie in der I. Variante.

View

Im View gibt es bei dieser Variante einen entschiedenen Unterschied zur I. Variante:

<%session
    scope="shared"
    include="Core/models/UserSession.h" >
        UserSession userSession;
</%session>
<%request
    scope="shared"
    include="Core/controller/AlterLogInController.h">
        AlterLogInController  alterLogInController(userSession);
</%request>
<%cpp>
    alterLogInController.operator(
        request,
        reply,
        qparam
    );
</%cpp>

<!DOCTYPE HTML>
<html>
<head>
    <meta charset = "UTF-8" />
</head>
<body>
        <form method="post" >
            <h2>Login</h2>
% if ( alterLogInController.feedback != "" ) {
            <div class="feedback-box">
                <b><$ alterLogInController.feedback $> </b>
            </div>
% }
            <p>Für den gewählten Bereich muss du angemeldet sein. Bitte
            authentifiziere dich...
            </p>
            <p>Benutzer:<br>
                <input
                    class="full-size"
                    name="arg_name"
                    type="text"
                    size="40"
                    maxlength="40">
            </p>
            <p>Passwort:<br>
                <input
                    class="full-size"
                    name="arg_password"
                    type="password"
                    size="40"
                    maxlength="40"></p>
            <p>
                <button name="arg_login_button"
                            value="pushed"
                            type="submit">Login
                </button>
            </p>
        </form>
        </p><a href="NewAccount">Hier</a> kannst du dir ein Account erstellen</p>
</body>
</html>

Dem Konstruktor der Klasse AlterLogInController wird die Instanz der Kasse UserSession als Parameter mitgegeben. Die Klasse AlterLogInController braucht die Klasse UserSession nämlich, um die Information über einen erfolgreichen Login zu hinterlegen, damit andere Komponenten auf diese Information zugreifen können. In dem Abschnitt,der mit dem Tag <%cpp> umschlossen ist, wird der Controller dazu aufgefordert den Request entgegen zu nehmen und zu verarbeiten. In den <%cpp>-Tags kann man ganz normalen C++-Code einbetten. Weiter unten im ecpp-Code werden die Controller-Eigenschaften genutzt, um den View mit generischen Werten zu befüllen.

% if ( alterLogInController.feedback != "" ) {

Routing

Damit der View unter der gewünschten Rute vom Browser gefunden wird, muss diese noch gesetzt werden: alterLogInController.feedback app.mapUrl( "^/LogIn", "LogInView" ); Jetzt wird das Loginfenter unter http://DieDomain.de/LogIn zu erreichen sein. Es ist möglich mehrere Routen zu ein und der selben Komponente zu definieren. Allerdings sollte man sein ACL-Konzept nicht auf Basis von URLs realisieren, weil man dann u.U. seine eigenen ACLs ungewollt austrickst. Die Regel „halte es einfach“ ist bei dem Thema Routing sicher zu empfehlen.


Creative Commons Lizenzvertrag

MVC-Architektur in Tntnet (Variante I.)

Standard

MVC und Tntnet

Tntnet macht dem Entwickler keine Vorschrift, wie er sein Projekt zu organisieren hat oder welche Paradigmen zur Anwendung kommen sollen. Tntnet konzentriert sich auf die Basisfunktionalität. Wenn eine höhere Abstraktion gewünscht ist, muss der Entwickler diese selber implementieren. C++ gibt es lange bevor es das heute sehr popoläre model view controller(MVC) pattern gab.  C++ wird es höchstwahrscheinlich noch geben, wenn das MVC-Pattern durch andere Konzepte abgelöst oder weiterentwickelt wurde. Das ist der Grund, warum das MVC auch nicht fester Bestandteil von Tntnet ist. Das heißt aber nicht, dass MVC nicht mit Tntnet gehen würde. Hier zeige ich eine denkbare Implementierung von MVC in Tntent.

Der Controller

Die Controller-Klasse wird von tnt::Component abgeleitet, und muss eine Funktion „operator()“ implementieren:

class MyCopmonentController : public tnt::Component
{
    public:
        unsigned operator() (
            tnt::HttpRequest& request,
            tnt::HttpReply& reply,
            tnt::QueryParams& qparam
        );
};

Da die Klasse kein Interface hat über die sie angesprochen wird, entfällt die Header-Datei. Es ist nur eine *.cpp erforderlich. Mit „qparam.arg<TYPE>(KEYWORD)“ kann ein Argument ausgelesen, das dem HTML-Requeset mitgegeben wurde. Als Beispiel:

// URL arguments
std::string arg_login_name =
    qparam.arg<std::string>("arg_login_name");

„TYPE“ ist der variable Type, den man zurück bekommen möchte. „KEYWORD“ ist der Bezeichner, mit dem der Wert übergeben wird. Möchte man eine Liste von Werten zurückbekommen, (z.B. aus Listen in HTML-Formularen, in denen eine Mehrfachauswahl erlaubt ist), muss eine andere Funktion genutzt werden. Diese heißt „args“ statt „arg“ und gibt einen Vector von Typen zurück, den man angibt:

std::vector<std::string>  args_userroles =
    qparam.args<std::string>("args_userroles");

Um nicht mit Argumenten und shared Variablen durcheinander zu kommen, kann es hilfreich sein, sich auf die Konvention zu einigen, dass Argumente mit den Präfix „arg_“ beginnen. Die Namen in den HTML-Formularen (bzw. der View in den *.ecpp-Datein) müssen natürlich der gleichen Konvention folgen.

<p>
 <label for="login_name">Login*:</label>
 <br>
 <input 
    class="full-size" 
    name="arg_login_name" 
    type="text" 
    value="<$ accountData.getLogin_name() $>" 
    maxlength="80"> 
</p>

Empfohlene Argumenten Typen

Hier eine tabellarische Übersicht, welcher C++-Cast für welchen HTML-Typ sinnvoll ist.

HTML-Type C++-Type
button bool
input/text string
input/password string
input/number int, long, short…
input/checkbox bool
select/multiple vector

Gemeinsam genutzte Variablen

Um Werte an den View zu übergeben, nutzt man shared-Opjekte und -Variablen. Diese müssen mit einem Macro registriert und initialisiert werden:

 
// shared variables
TNT_REQUEST_SHARED_VAR( UserSession, s_userSession, ());

Der erste Parameter ist der Typ; der zweite Name und der Dritte ist der aufzurufende Constructor. Wenn dieser einen Parameter braucht, kann dieser hier angegeben werden. Es empfehlt sich für  die Übersicht die Namenskonvention zu verwenden, die shared Variablen ein „s_“ als Präfix voranstellen.

Es gibt für die shared Opjekte verschiedene Gültigkeitsbereiche bzw. Lebensdauer. So werden über „TNT_SESSION_GLOBAL_VAR“ die Objekte die gesamte Session überdauern. Es gibt noch „TNT_REQUEST_SHARED_VAR“. Hier haben die Objekte nur eine Lebensdauer für ein Request. Es ist ratsam mit „TNT_SESSION_GLOBAL_VAR“ sehr sparsam umzugehen und wenn möglich, nur mit „TNT_REQUEST_SHARED_VAR zu“ arbeiten. Andernfalls kann es zu ungewollten Effekten kommen, wenn Objekte von einer vorigen Request-Prozedur noch mit einen altem Wert belegt sind.

Damit der Controller tatsächlich beim Routing berücksichtigt wird, muss die Klasse der Component-Factory bekannt gemacht werden:

static tnt::ComponentFactoryImpl<MyCompController>
     factory("MyCompController");

Model

Im Controller (und später im View) wird eine Klasse UserSession als shared variables verwendet. Es ist im Prinzip möglich jeden Typ, also auch primitive Typen wie int zu benutzen. Davon ist jedoch abzuraten, weil es keine Namensräume gibt, die die einzelnen Komponenten trennen können, wenn sie von der Tntnet-Component-Factory verwaltet werden. Die Wahrscheinlichkeit bei sehr großen Projekten aus Versehen einen Variablennamen zu verwenden, der schon an anderer Stelle verwendet wird, ist sehr groß.

Kapselt aber jede Komponente (logische Einheit einer Website) ihre Session-Daten in eine eigene Klasse (bzw. Model) – wie hier – können verschiedene Komponenten, die selben Variablennamen haben, verwenden. Durch die Verwendung unterschiedlicher Typen (Klassen) werden sie von Tntnet als unterschiedliche Variablen verwendet. Zur Veranschaulichung:

Ist in dem Controller der Komponente A folgender Code zu finden

TNT_SESSION_SHARED_VAR(CompA::SessionShareds, sessionInfo, ());

und in dem Controller der Komponente B folgender

TNT_SESSION_SHARED_VAR(CompB::SessionShareds, sessionInfo, ());

so benutzen beide Komponenten zwar den gleichen Instanznamen aber unterschiedliche Klassen-Typen, so dass sie sich nicht gegenseitig ihre Werte überschreiben.

View

Der View wird in Tntnet mit einer erweiterten HTML-Auszeichnungssprache namens epcc erstellt und vom Precompiler ecppc in C++-Code umgewandelt. Ich werde bestimmt noch in einem späteren Artikel detaillierter auf diese Aufzeichnungssprache eingehen. Bis dahin sei vorerst noch auf die offizielle eng. Doku verwiesen: http://www.tntnet.org/man7/ecpp.7.html An diese Stelle gehe ich erst mal nur soweit darauf, ein wie es das MVC-Thema berührt.

Damit die shared Variablen des Controllers auch dem View zur Verfügung stehen, müssen diese der View-Umgebung bekannt gemacht werden. Das geschieht auf die folgende Weise:

<%session
    scope="shared"
    include="models/UserSession.h">
        UserSession s_userSession;
        std::vector<std::string> s_allRolls;
</%session>

<%request
    scope="shared">
            std::vector<std::string> sh_allRolls;
</%request>

Mit dem scope-Wert „shared“ wird angezeigt, dass es sich um shared Variablen handelt. Mit „include“ können benötigte Header-Dateien eingebunden werden. In diesem Fall includieren wir die Klasse „UserSession“. Diese wird gebraucht, damit der Type „UserSession“ dem Compiler bekannt ist. Zwischen den Tags werden die eigentlichen Variablen aufgelistet bzw. bekannt gemacht. In dem Beispiel sieht man auch, dass hier bei der Lebensdauer der shared Variablen differenziert wird. Im Tag „session“ kommen alle Werte, die zuvor mit dem Makro „TNT_SESSION_GLOBAL_VAR“ deklariert wurden. In „request“ kommen alle Variablen, die in dem Controller „TNT_REQUEST_SHARED_VAR“ initialisiert wurden.

Routing

Damit der View und der Controller tatsächlich gemeinsam eine Anfrage bearbeiten, müssen sie noch mit einer gemeinsamen Route verknüpft werden.

    app.mapUrl( "^/(.*)$", "$1Controller" );
    app.mapUrl( "^/(.*)$", "$1View" );

Diese Regel sagt aus, dass jede URL einmal um „Controller“ und einmal um „View“ ergänzt wird, und damit zuerst der Conntroller und dann der View aufgerufen wird. Lautet nun unser Controller z.B. „MyCompController“ und der Viel „MyCompView“, so wird die neue Kompnent über die URL „MyComp“ aufgerufen.

Sicherheitsaspekt

Der View sollte keinerlei Logik enthalten. Sollte einmal wegen eines Fehlers oder einer Fehlkonfiguration der Controller nicht aufgerufen werden, liefert der View trotzdem seinen Inhalt ab. Wenn die Zugriffsrechte im Controller realisiert sind, greifen diese nicht mehr und der View könnte Daten preisgeben, die nicht für den Besucher gedacht sind. Deshalb sollte IMMER der Controller für die Befüllung mit Inhalt und Daten zuständig sein.

Troubleshooting

Der Controller beim MVC wird (scheinbar) nicht aufgerufen

Problem: Man hat eine Komponente mit dem MVC-Konzept erstellt, aber der Controller scheint nicht aufgerufen zu werden.

  • Überprüfen, ob das wirklich zutrifft, indem z.B. eine Testausgabe mit std::cout auf die Standartausgabe ausgegeben wird
  • Kontrollieren, ob die Route stimmt, die über app.mapUrl() oder über das Konfigurations-File von Tntnet gesetzt wurde
  • Prüfen, ob der Name des Controller korrekt in der Component-Factory angegeben wurde: static tnt::ComponentFactoryImpl factory(„MyController“);
  • Kontrollieren, ob die Komponente compiliert und gelinkt wurde, indem das Makefile überprüft wird
  • Prüfen, ob ggf. form-Tag im View  die richtige Componente aufruft

Nachwort

Es werden sich schon einige Leser Gedanken gemacht haben, warum im Titel „(Variante I.)“ steht. Tntnet gibt den Entwickler die Freiheit, die Dinge so zu tun, wie man sie für richtig hält. Was „richtig“ ist, da gehen die Meinungen meist auseinander. Deshalb gibt es auch noch andere Lösungen. Ich werde noch in ein weiteres MVC-Pattern für Tntnet vorstellen. Auch das Thema „Verzeichnisstruktur in Tntnet-Projekten“ werde ich noch ein mal separat behandeln.


Creative Commons Lizenzvertrag

Verzeichnisstruktur in Tntnet-Projekten

Standard

Es macht immer Sinn, sich über die Verzeichnisstruktur seines Codes Gedanken zu machen. Gerade bei größeren Projekten mit Arbeitsteilung, sollte die Arbeitsteilung auch durch die Ordnerstruktur unterstützt werden. Die Verwendung der Namensräume sollte selbstverständlich möglich konform zur Ordnerstruktur sein. Das gelingt mir in der Praxis auch nicht immer 100%, aber es ist sehr hilfreich, wenn man sich in fremden Code einarbeiten muss.

Bei der Ordnerstruktur macht Tntnet dem Entwickler keinerlei Vorschriften, wie diese auszusenden hat. Gerade wenn ein umfangreicher Code nicht umgeschrieben werden muss, wird man das sehr zu schätzen wissen. Ist man aber aber tatsächlich in der komfortablen Situation, ein völlig neues Programm zu schreiben ohne Altlasten, möchte man sich vielleicht doch an einem best practice orientieren. So könnte eine Verzeichnisstruktur aussehen….

/projektname
/projektname
/projektname/src
/projektname/src/Core
/projektname/src/Core/initcomponent.h
/projektname/src/Core/views
/projektname/src/Core/manager
/projektname/src/Core/models
/projektname/src/Core/controller
/projektname/src/Core/resources
/projektname/src/component_1/
/projektname/src/component_1/initcomponent.h
/projektname/src/component_1/views
/projektname/src/component_1/manager
/projektname/src/component_1/models
/projektname/src/component_1/controller
/projektname/src/component_1/resources
/projektname/src/component_2/
/projektname/src/component_2/initcomponent.h
/projektname/src/component_2/views
/projektname/src/component_2/manager
/projektname/src/component_2/models
/projektname/src/component_2/controller
/projektname/src/component_2/resources

Unterhalb von /src sind die Sourcen zu finden. Um das Projekt in logische Einheiten zu unterteilen, gibt es darunter ein Verzeichnis pro Komponente. Die Unterteilung soll später dabei helfen, dass Entwickler-Teams in verschiedenen Bereichen arbeiten können ohne sich gegenseitig zu behindern. Zudem soll eine leichtere Wiederverwertbarkeit von Komponenten damit gefördert werden. Es empfiehlt sich deshalb, die einzelnen Komponenten auch durch Namensräume von einander zu trennen. Z.B:

ProjectName::ComponentOne::View

Für das Kernmodul, in dem die grundliegenden Kernfunktionen der Webapplikation abgelegt sind,  empfiehlt es sich einen Namensraum wie „ProjectName::Core“ zu verwenden, natürlich mit einem gleichnamigen Verzeichnis „/Core“.

Komponenten-init-Funktion

Ich rate auch zu der Konvention, für jede Komponente eine Funktion zur Initialisierung der Komponente bereit zu stellen, die in einer Datei „initcomponent.h“ abgelegt ist und den gleichen Namen trägt ( also: „initcomponent()“). Diese Funktion sollte im Haupt- Programm (wahrscheinlich in der main()-Funktion) aufgerufen werden. Diese Funktion sollte alle notwendigen Vorbereitungen treffen, die für die Arbeit der Komponente wichtig sind. Das könnte z.B. die Konfiguration des Routings sein, oder die Initialisierung einer Random-Funktion für bessere Zufallszahlen.


Creative Commons Lizenzvertrag

C++-Webframeworks in Übersicht: CppCMS

Standard

Bei meiner Suche nach geeigneten Webframeworks bin ich auf verschiedene Projekte gestoßen, die ich hier in einer kleinen Serie kurz vorstellen möchte:

Das CppCMS-Framework gibt es seit 2008. Es steht unter einer LGPLv3 license und alternativ unter einer Commercial License.  Das Projekt hat zwei Anwendungen an Hand deren man gleich die Praxistauglichkeit überprüfen kann.  Das ist zum einen WikiPP ein Wiki und zum Anderen CppBlog. Beides nennt sich „Engines“. Per Wikipedia-Definition ist eine Engine „[…] in der Informationstechnologie ein eigenständiger Teil eines Computerprogramms.“ Also darf man wohl keine „schlüsselfertige“ Anwendung erwarten. Für die Anbindung an Datenbanken gibt es noch ein zusätzliches Framework das CppDB. Es deckt die Datenbanken MySQL, PostgreSQL und Sqlite3 ab.

ohloh.net code analyses

Ich habe weder CppCMS noch CppBlog und WikiPP getestet. Was mich abgeschreckt hat, war die geringe Aktivität des Projektes. Es sah für mich tot aus. Ich habe auch keine Mailing-Liste gefunden, auf der man sich hätte einschreiben können. Ganz tot scheint das Projekt aber nicht zu sein den der letzte Post auf dem Weblog ist vom 24.6.2013.

Siehe auch: „C++-Webframeworks in Übersicht: Als Tabelle“


Creative Commons Lizenzvertrag

C++-Webframeworks in Übersicht: Als Tabelle

Standard

Bei meiner Suche nach geeigneten Webframeworks bin ich auf verschiedene Projekte gestoßen, die ich hier in einer kleinen Serie kurz vorstellen möchte. Hier eine tabellarische Übersicht, über die vier wichtigsten Projekte:

Projektname Wt (Witty) CppCMS Tntnet Treefrog
Projektstart 2009 2008 2005 2011
Abstraktionslevel Sehr hoch Mittel Mittel Sehr hoch
Aktive Entwickler >5 1 >1 >2
Code-Zeilen 285.620 1.839.480 260.388 51.335
Prepompiler Nein Ja Ja Ja
Abhängigkeiten
  • CMake
  • Boost-Lib
  • OpenSSL  (Opt.)
  • Pango (Opt.)
  • Boost-Lib
  • Python 2.x
  • CgiCC
  • libgcrypt
  • gettext
  • cxxtools
  • automake*
  • autoconf*
  • libtool*
  • postgresql-devel**
  • zlib-devel
  • openssl-devel
Qt
Lizenz
  • Open  Source
  • proprietär
  • Open Source
  • proprietär
OpenSource OpenSource
Datenbank-
unterstüzung
  • Firebird
  • SQLite
  • PostgreSQL
  • MySQL
  • SQLite
  • PostgreSQL
  • MySQL
  • SQLite
  • PostgreSQL
  • MySQL
  • Oracle
Über Qt:

  • MongoDB
  • SQLite
  • PostgreSQL
  • MySQL
Mailingliste Ja (sehr aktiv) Nein Ja Ja
Authentifikations-
methoden
  • Facebook
  • Google
  • E-mail-Token
  • SHA1
  • MD5
  • OAuth
  • Token
Extras E-mail-Versand
  • Serialisierung
    • XML
    • Json
    • CSV
  • Logging
  • Komplexes URL-Routing
  • REST
    • XML
    • Json
    • Binary
    • CSV
  • SOA
  • Validation
  • Mailer
  • Logging
  • Plugin
  • Image Manipulation
Pakete in
Distributionen
Ja Nein Ja Nein
Dokumentation Mittel,
viele Beispiele
Wenig Mittel,
viele Beispiele
Mittel
OS
  • Linux
  • Windows/Cygwin
  • MacOS
  • Android
  • Raspberry Pi
  • QNX
  • Solaris
  • Linux
  • Windows/Cygwin
  • MacOS
  • FreeBSD
  • Linux
  • UNIX-like OS
  • Linux
  • Windows
  • UNIX-like OS
  • MacOS

Erläuterungen
* Abhängigkeit wenn die Bibliothek selber übersetzt wird.
** Abhängigkeit optionaler Funktionen.


Creative Commons Lizenzvertrag