development

  • In bash you can set a variable to ‚readonly‘. This can be very useful to prevent accidental overwrites.

    readonly INSTALLDIR="/opt/goodSoftware"
  • https://martinfowler.com/articles/modularizing-react-apps.html

  • Im folgenden teile ich hier einige Gedanken zu dem Papier:

    „Harmonisiertes Metadatenschema für die DSpace-Repositorien der Berliner Universitäten – Ergebnis der Arbeitsgruppe DSpace Metadaten bestehend aus Mitgliedern der Charité – Universitätsmedizin Berlin, der Freien Universität Berlin, der Humboldt-Universität zu Berlin und der Technischen Universität Berlin“

    Zu finden hier: https://refubium.fu-berlin.de/handle/fub188/37260

    Direktlink zum Datensatz hier:  https://refubium.fu-berlin.de/bitstream/handle/fub188/37260/Berlin_DSpace_MDS.xlsx?sequence=1&isAllowed=y&save=y

    Als Entwickler finde ich die im Abstract formulierten Zielsetzungen besonders wichtig und erfreulich:

    „Ziel ist, innerhalb der Berliner Universitäten einen einheitlichen
    Gebrauch der Metadaten zu gewährleisten. Gleichzeitig wird es bei
    Einführung des neuen Modells möglich sein, Mappingtabellen, Schnittstellen
    und Programmierarbeiten zwischen den beteiligten Einrichtungen leichter
    auszutauschen.“

    Trigger

    Das Arbeitsergebnis wurde als Excel-Datei veröffentlicht. Dies reicht als Grundlage für einen gelungenen Datenaustausch natürlich noch nicht aus. Typische Folgefragen von Softwareentwicklern sind:

    – Wo finde ich die konkreten Schemadateien?

    – Gibt es Testsysteme?

    – Welche Protokolle/APIs sollen zum Austausch der Daten angeboten und genutzt werden?

    – Wie sieht es mit Mehrsprachigkeit in den Metadaten aus?

    Aber zurück zum vorliegenden Datensatz:

    Beobachtung

    An vielen Stellen in der Excel-Datei steht als Datentyp Freitext. Es gibt kaum MetaMetadaten. An einigen Stellen erscheint das Modell zu flach und zu spezifisch. Dies ist in gewisser Weise Schade, da hier die zitierte. Zielsetzung möglicherweise durch „ein paar Handgriffe“ optimaler unterstützt werden könnte.

    Beispiel aus Zeile 22-24:

    Feld Beispielwert
    dc.subject open access
    dc.subject.ddc 300 Sozialwissenschaften
    dc.subject.rvk AK 54355

    Ein paar Dinge fallen hier auf:

    1. In den Freitextfeldern werden Notationen und Label gemischt

    2. Für einzelne Notationssyteme existieren spezielle Unterfelder (ddc,rvk). Dies ist nicht gut erweiterbar. Besser wäre es zu jedem Subject das Notationssystem zu vermerken und für diesen Vermerk seinerseits ein kontrolliertes Vokabular (z.B. basierend auf Wikidata, s.u.) zu definieren.

    3. Es wird nicht ganz klar, wie Mehrsprachigkeit realisiert werden soll.

    4. Es gibt kein (Sub)Feld indem z.B. URIs auf SKOS-Vokabulare mitgeführt werden könnten.

    These

    Um den Bereich „subject“ besser maschinell nachnutzbar zu machen, sollten kontrollierte SKOS-Vokabulare die Regel sein. Wenn möglich sollten URIs zur Identifikation von verwendeten Termen mit im Datensatz gespeichert werden.

    In jedem Fall sollten Notationen, Label und IDs im Datenmodell in getrennte Unterfelder laufen. Labels sollten mehrsprachig im Datensatz mitgeführt werden können bzw. über eine URI leicht nachgeladen werden können.

    Beispiel und Vorschlag

    Das Beispiel zeigt ein Schlagwort im Feld subject mit zusätzlichen Informationen.

    "subject":[{
       "id":"http://dewey.info/class/300",
       "notation":"300",
       "prefLabel": "Sozialwissenschaften",
       "label":{
          "de":"Sozialwissenschaften"
          "en": "Social Sciences"
          "fr":"...."
        },
       "source":{
          "id":"https://www.wikidata.org/wiki/Q15222117",
          "label":{
            "en": "Dewey Decimal Classification"
         }
        "similarTo": "https://www.wikidata.org/wiki/Q34749"
       }
    }]

    Zusätzlich zu dem Schlagwort werden Informationen zur Quelle und zum Notationssystem gegeben. Notation und Schlagwort werden getrennt. Es wird über einen Link auf Wikidata eine Verlinkung in andere Schlagwortsysteme realisiert.

    Und was bringt das?

    Dies hat vor allem Vorteile auf der maschinellen Konsumentenseite.

    1. Alle subjects, egal aus welchem Vokabular, können zunächst mal leicht verarbeitet werden. Folgendes geht z.B. immer: subject.0.prefLabel, subject.0.label.de . Hierbei ist es egal, aus welchem Notationssystem ein Schlagwort kommt. Dies stellt eine erhebliche Vereinfachung für Aggregatoren dar, die für einfache Anwendungsfälle keine weiteren Informationen über die angebotenen Subfelder eines Quellsystems haben müssen. Siehe Punkt 4.
    2. Über die „source“ und die „notation“ können einerseits in einer grafischen Oberfläche zusätzliche Hinweise gegegeben werden, andererseits können die nachnutzenden Systeme Verknüpfungen zu anderen Datensätzen herstellen und so sehr effektive Browsingoberflächen oder sonstige Bezugssysteme aufbauen.
    3. Wikidata als ein mögliches Werkzeug um Verknüpfungen herzustellen. Über den Link unter „similarTo“ können weitere Vokabulare verknüpft werden. Sehr praktisch wenn man z.B. die Inhalte unterschiedlicher Quellen mit unterschiedlicher Verschlagwortung/Erschließung aggregieren möchte.
    4. Durch den Verzicht auf Unterfelder für spezifische Vokabulare, z.B. .ddc oder .rvk , wird ein einheitlicher Zugriff auf Schlagworte aus unterschiedlichen Vokabularien unterstützt. Dadurch können konsumierende Systeme auch Schlagwortsysteme, die sie nicht vollständig unterstützen einfach zur Anzeige bringen und indexieren, etc..
    5. Anzeige und intellektuelle Erschließung werden separiert. Die Anzeige eines Schlagworts (das Label) wird von der verwendeten Notation getrennt. Dies eröffnet nachnutzenden Systemen die Möglichkeit kontextspezifische Anzeigestrategien zu etablieren ohne die zugrundeliegende intellektuelle Erschließung (in Form der Notation) anpassen zu müssen. Ersteres ist oft gewünscht, letzteres ist oft nicht ohne weiteres möglich.
    Das sieht aber total aufwendig aus.
    Vielleicht ist es gar nicht so aufwendig. Gerade bei der Erstellung der Metadaten liegen die Infos zu einem Schlagwort oft alle im Erfassungssystem vor. Sie müssten dann nur abgespeichert werden!
    Ist das alles?
    Ich denke, nein. Die Design-Prinzipien, die ich hier für das Subject Feld beispielhaft ausgeführt habe, können auch für andere Felder sinnvoll zur Anwendung gebracht werden. z.B. für Contributor, Insitutionen etc. Auch in diesen Fällen bietet eine hierarchisierte Form der Speicherung Vorteile gegenüber der Schaffung neuer Felder und vereinfacht die Konsumierbarkeit der Daten.
  • Blogpost for this stackoverflow answer: https://stackoverflow.com/a/74701083/1485527

    Get code here

    Create example app

    npx create-react-app read-env-example --template typescript
    

    Navigate to fresh app

    cd read-env-example
    

    Create Dockerfile

    mkdir -p docker/build
    

    docker/build/Dockerfile

    # build environment
    FROM node:19-alpine3.15 as builder
    WORKDIR /app
    ENV PATH /app/node_modules/.bin:$PATH
    
    COPY package.json ./
    COPY package-lock.json ./
    RUN npm ci
    RUN npm install react-scripts@5.0.1 -g
    COPY . ./
    RUN PUBLIC_URL="." npm run build
    
    # production environment
    FROM nginx:stable-alpine
    COPY --from=builder /app/build /usr/share/nginx/html
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    
    COPY docker/build/docker-entrypoint.sh /
    RUN chmod +x docker-entrypoint.sh
    
    ENTRYPOINT ["/docker-entrypoint.sh"]
    

    Create docker-entrypoint.sh

    This script will be executed at container start.
    It generates the config.js file containing all environment variables starting with ‚MYAPP‘ under window.extended.

    docker/build/docker-entrypoint.sh

    #!/bin/sh -eu
    
    
    function generateConfigJs(){
        echo "/*<![CDATA[*/";
        echo "window.extended = window.extended || {};";
        for i in `env | grep '^MYAPP'`
        do
            key=$(echo "$i" | cut -d"=" -f1);
            val=$(echo "$i" | cut -d"=" -f2);
            echo "window.extended.${key}='${val}' ;";
        done
        echo "/*]]>*/";
    }
    generateConfigJs > /usr/share/nginx/html/config.js
    
    nginx -g "daemon off;"
    

    Create docker-compose.yml

    mkdir docker/run
    

    docker/run/docker-compose.yml

    version: "3.2"
    services:
    
      read-env-example:
        image: read-env-example:0.1.0
        ports:
          - 80:80
        env_file:
          - myapp.env
    

    Create runtime config for your app

    docker/run/myapp.env

    MYAPP_API_ENDPOINT='http://elasticsearch:9200'
    

    Create config.js <– this is where .env will be injected.

    public/config.js

    /*<![CDATA[*/
    window.extended = window.extended || {};
    window.extended.MYAPP_API_ENDPOINT='http://localhost:9200';
    /*]]>*/
    

    Note: This file will be completely overwritten by the docker-entrypoint.sh. For development purposes you can set it to any value that is appropriate, e.g. when used together with npm start.

    Include config.js in index.html

    public/index.html

       <head>
         ...
        <script type="text/javascript" src="%PUBLIC_URL%/config.js" ></script>
         ...
       </head>
       <body>
    

    Make use of your environment variable

    src/App.tsx

    declare global {
        interface Window { extended: any; }
    }
    function App() {
      return (
        <div className="App">
          <header className="App-header">
    
              You have configured {window.extended.MYAPP_API_ENDPOINT}
    
          </header>
        </div>
      );
    }
    

    Build

    npm install
    

    Create docker image

    docker build -f docker/build/Dockerfile -t read-env-example:0.1.0 .
    

    Run container

    docker-compose -f ./docker/run/docker-compose.yml up
    

    Navigate to your app

    Open http://localhost in your browser.
    You will see the content of MYAPP_API_ENDPOINT like  provided in your docker/run/myapp.env.

    Further usage

    You can provide additional variables starting with MYAPP. The docker-entrypoint.sh script will search for all variables starting with MYAPP and make them available through the windows object.

  • For most people, the word “integration” creates the impression of connecting systems together, of sharing data to keep systems in sync. I believe that definition of integration is insufficient to meet the demands of a modern digital business, and that the real goal of integration done well is to create clean interfaces between capabilities.

    https://martinfowler.com/articles/cant-buy-integration.html#PutMostOfYourEnergyIntoBuildingCleanInterfaces

  • At the example of this stackoverflow question:

    „Supposing we have the following triple in Turtle syntax:

    <http:/example.com/Paul> <http:/example.com/running> <http:/example.com/10miles> .
    

    How do I add a start and end time? For example if I want to say he started at 10 am and finished his 10miles run at 12 am. I want to use xsd:dateTime.“

    https://stackoverflow.com/questions/49726990


    Sometimes it can be hard to create good, well fitting models. In my own experience it is crucial to identify a well defined set of entities and relations to create a vocabulary from.  Some people prefer to use visual strategies to develop their models. I prefer to write models in structured text.  This has the advantage that the process of modeling directly leads into actual coding.

    Here is an example on how I would tackle the question .

    1. The modelling part (not much RDF involved)

    {
        "runs": [
            {
                "id": "runs:0000001",
                "distance": {
                    "length": 10.0,
                    "unit": "mile"
                },
                "time": {
                    "start": "2018-04-09T10:00:00",
                    "end": "2018-04-09T12:00:00"
                },
                "runner": {
                    "id": "runner:0000002",
                    "name": "Paul"
                }
            }
        ]
    }
    

    We store the json document in a file run.json. From here we can use the ‚oi‘ command line tool , to create an adhoc context.

    oi run.json -t context

    The resulting context is just a stub. But with a few additions we can easily create a context document to define id’s and types for each vocable/entity/relation.

    2. The RDF part: define a proper context for your document.

       {
        "@context": {
            "ical": "http://www.w3.org/2002/12/cal/ical#",
            "xsd": "http://www.w3.org/2001/XMLSchema#",
            "runs": {
                "@id": "info:stack/49726990/runs/",
                "@container": "@list"
            },
            "distance": {
                "@id": "info:stack/49726990/distance"
            },
            "length": {
                "@id": "info:stack/49726990/length",
                "@type": "xsd:double"
            },
            "unit": {
                "@id": "info:stack/49726990/unit"
            },
            "runner": {
                "@id": "info:stack/49726990/runner/"
            },
            "name": {
                "@id": "info:stack/49726990/name"
            },
            "time": {
                "@id": "info:stack/49726990/time"
            },
            "start": {
                "@id":"ical:dtstart",
                "@type": "xsd:dateTime"
            },
            "end": {
                "@id":"ical:dtend",
                "@type": "xsd:dateTime"
            },
            "id": "@id"
        }
    }

    3. The fun part: Throw it to an RDF converter of your choice

    This is how it looks in JSON-Playground

    Or simply use ‚oi‘:

     
    oi run.json -f run.context -t ntriples
    

    Prints:

     
    _:b0 <info:stack/49726990/runs/> _:b3 .
    _:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <info:stack/49726990/runs/0000001> .
    _:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
    <info:stack/49726990/runs/0000001> <info:stack/49726990/distance> _:b1 .
    <info:stack/49726990/runs/0000001> <info:stack/49726990/runner/> <info:stack/49726990/runner/0000002> .
    <info:stack/49726990/runs/0000001> <info:stack/49726990/time> _:b2 .
    _:b1 <info:stack/49726990/length> "1.0E1"^^<http://www.w3.org/2001/XMLSchema#double> .
    _:b1 <info:stack/49726990/unit> "mile" .
    <info:stack/49726990/runner/0000002> <info:stack/49726990/name> "Paul" .
    _:b2 <http://www.w3.org/2002/12/cal/ical#dtend> "2018-04-09T12:00:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
    _:b2 <http://www.w3.org/2002/12/cal/ical#dtstart> "2018-04-09T10:00:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
    
  • https://github.com/jschnasse/oi

    wget https://dl.bintray.com/jschnasse/debian/oi_0.5.10.deb
    sudo apt install ./oi_0.5.10.deb