Skip to content

Blog

Shared Recordset Definition

I think that the modules I developed for Sentosa Autoforms are quite elegant and flexible (expecially the Sentosa::SQL module, and the DataDatables and JSON modules) so I decided to reuse them! However I don't like the fact that Sentosa Autoforms is all driven by database data.. while it's sometimes very convenient, I don't like the layer of complexity that it adds.

So I started to work on it, trying to figure out a simpler solution!

First of all, I needed to store all recordset information on a variable, shared to all components. The hashref recordset goes to the Base.mp pure perl component:

my $recordset = {
  # Allarms
  "alarms" => {
    description => 'Alarms',
    connection => { db => 'dbi:Pg:dbname=dbname;host=10.10.10.10', username => 'myusername', password => 'mypassword' },
    source => "alarms",
    pk => 'id',
    columns => [
        { col => 'paziente',                 caption => 'Paziente', type => 'text', 'link-id' => '4' },
        { col => 'data_nascita',             caption => 'Data Nasc', type => 'text' },
        { col => 'dataora',                  caption => 'Data/ora PS', type => 'text' },
        { col => 'osp_ps_priorita_ingresso', caption => 'Priorità', type => 'text' },
        { col => 'link_fascicolo',           caption => 'Fascicolo', type => 'hidden' }
      ],
    json => 'json/alarms.json'
  },
}

Pretty elegant! Also I can use all kinds of Perl stuff to make it more tidy or more compact (e.g. I can specify a connections hash_ref, and I can calculate the array_ref columns somehow.. also, Sentosa has to process every query every time we access a recordset, while with this systems I can pre-calculate all queries at runtime the first time we access it).

How can I share this recordset data to all components? I can specify a method at the end of the Base.mp component:

method getrecordset($rs) {
  return $recordset->{$rs};
}

and then the default handler inside the json directory can be just like this:

<%flags>
extends => '../Base.mp';
</%flags>
%class
  has '_id';
  has '_app';

  has 'iDisplayStart';
  has 'iDisplayLength';
  has 'iColumns';

  has 'sEcho';
</%class>
<%init>
  my ($recordset_name, $ext) = $m->path_info =~ /(.*)(\.json)$/;
  if (! $.getrecordset($recordset_name)) {
    $m->not_found();
  }
</%init>
<&
  ../query-data-widget.mi,
    obj => {
      source => $.getrecordset($recordset_name)->{source},
      pk     => $.getrecordset($recordset_name)->{pk},
      db     => $.getrecordset($recordset_name)->{connection}->{db},
      name   => 'allarmi_osp_ricovero',
      description => $.getrecordset($recordset_name)->{description},
      username => $.getrecordset($recordset_name)->{connection}->{username},
      password => $.getrecordset($recordset_name)->{connection}->{password}
    },
    columns => $.getrecordset($recordset_name)->{columns},

    iDisplayStart => $.iDisplayStart,
    iDisplayLength => $.iDisplayLength,
    iColumns => $.iColumns,
    sEcho => $.sEcho,
    searchArgs => $.args
&>

it will inherit only from the Base.mp (no html, only perl!) ... and yes, I know this is a little ugly and not too elegant, but at least I can use the query-data-widget (and the query-widget as well) without major updates. I will work on making it better :)

Ah only one thing has to be updated! instead of working on the $.columns object I figured out that I have to work on a local copy of the object:

    use Clone 'clone';
    my $local_columns = clone($.columns);

otherwise, everytime I modify the columns object to add a temporary filter, the next time the filter is still there - because it's a hash_ref!

To call the query widget I use this:

<&
  query-widget.mi,
    query => {
      id => 'osp-ps',
      json => $.getrecordset($recordset_name)->{json},
      description => $.getrecordset($recordset_name)->{description},
      pk => $.getrecordset($recordset_name)->{pk},
      columns => $.getrecordset($recordset_name)->{columns},
      params => { 'table_link'  => undef, 'hide_title'  => 1, 'hide_top'    => 1, 'hide_bottom' => 0 }
    }
&>

Well this environment is much more comfortable, I think I can add plenty of improvements soon, and I think I can write a good manual of how to use those components. See you soon!

SQL Markdown Builder

I like text editors, especially Sublime Text. And I like to work at the command line (on Linux, on Mac... but even on Windows 10 it has become very nice).

So it's pretty normal that everything I write is in Markdown syntax!

Since I work every day with SQL, and every day I have to prepare quick reports, extract some data, share some tables, share some rows... I needed a tool to run a SQL query against a database that returns a table in Markdown format!

This way I can easily copy & paste to a new mail, and format it nicely and professionally with markdown-here, quickly and without becoming crazy.

So I quickly wrote this tool and I called SQL Markdown Builder because it can easily be integrated with Sublime Text, even if it is not a native plugin.

Yes I know this is a little off topic from Mason and Sentosa, but this tool is written in Perl and... I included it in Sentosa anyway! So let's start!

Getting Started

Make sure you have installed perl, File::Slurp, Getopt::Long, DBI, and the DBD libraries for your database:

cpanm File::Slurp
cpanm Getopt::Long
cpanm DBI
cpanm DBD::SQLite
cpanm DBD::Pg
cpanm DBD::mysql
...

Run a query

Once everything is installed, you can write a single query, or more queries separated by a ; in a .SQL text file:

drop table if exists gardens;

create table gardens (
    id integer primary key,
    name varchar(100),
    city varchar(100)
);

insert into gardens (name, city) values
('Gardens By The Bay', 'Singapore'), ('Hyde Park', 'London'),
('Central Park', 'New York'), ('Villa Borghese', 'Rome'),
('Princes Street Gardens','Edinburgh');

select * from gardens;

and you can run your query file at the command line:

perl sqlbuild.pl -c dbi:SQLite:dbname=test.sqlite3 -s query.sql

the connection string is in the DBI format, this example is for SQLite so we don't need to specify a username or a password.

SQL Markdown Builder will execute every single query in sequence against the specified database. If the query is an INSERT or an UPDATE query, it will return '0 rows', otherwise it will return the results in Markdown format (nicely aligned):

0 rows
0 rows
0 rows

id | name                   | city
---|------------------------|----------
1  | Gardens By The Bay     | Singapore
2  | Hyde Park              | London
3  | Central Park           | New York
4  | Villa Borghese         | Rome
5  | Princes Street Gardens | Edinburgh

If some columns become too big, you can specify the maximum size of a column with -mw parameter (or use -h to see all parameters).

Instead of using the command line, you can also specify all connection strings, usernames, passwords inside the .SQL file itself:

/*
  conn="dbi:SQLite:dbname=test.sqlite3"
  username=""
  password=""
*/

This is very handy but not too secure (other users might peek inside your files, and also updating a password might become complicated).

Integration with Sublime Text

This is for Windows, but Linux and OSX will be very similar.

Just get the provided file Sql-mk-build.sublime-build, update the working_dir:

{
    "cmd": ["perl", "sqlbuild.pl", "-s", "$file" ],
    "working_dir": "c:\\GitHub\\Sql-mk-builder\\"
    "selector": "*.sql"
}

and move it to the build directoy:

C:\Users\YOURUSERNAMEHERE\AppData\Roaming\Sublime Text 3\Packages\User

then you can edit your .SQL files with Sublime Text, and see the results using CTRL+B.

Happy SQL & Markdown!

Systemctl start sentosa.service

I just started to publish my Sentosa web portal and users of my company are now starting to use it. Great!

I wanted to add a service on my CentOS server and I wanted to manage it with Systemd.

First I created a /etc/systemd/system/sentosa.service file that contains:

[Unit]
Description=Sentosa Autoforms
After=network.target

[Service]
ExecStart=/usr/local/bin/plackup -E production --port 5001 --access-log /var/www/apps/sentosa/logs/access.log /var/www/apps/sentosa/bin/app.psgi

the app.psgi is the standard file from GitHub, I just disabled the debug mode.

Now I can start, restart, stop my service with:

systemctl start sentosa.service
systemctl restart sentosa.service
systemctl stop sentosa.service

And I can monitor the tail of the logs with:

journalctl -u sentosa.service -f

DB.pm Library

Hello, I'm finally back! Yes not much posts lately, but lots of coding - it's a lot considering that I'm working full time and that this is only a side project on my spare time.

After some previous attempts, I decided that I had to write a library to manage queries, to apply filters and searches, to move to the first/last/previous/next record, and to update and insert data.

I finally came out with DB.pm library, and even if it still needs to be improved I'm very proud of how small, elegant and clean it is!

I might rename it to Sentosa::SQL as I might reuse it on other projects as well.

Syntax

You need to define a $columns array reference as the following:

my $columns = [
  { col => 'id', 'pk' => 1},
  { col => 'name'},
  { col => 'surname'}
];

where id is the primary key, and then you have to call selectQuery and get a hash reference like this:

my $q = Sentosa::Db::selectQuery(
  'mytable',
  $columns,
  'ASC',
  undef,
  0, #offset
  10, #number of records
  'SQLite'
);

this will return three queries and three array references like below:

  print $q->{query}, "\n";
  print "(".join(',', @{$q->{query_data}}).")\n";

  print $q->{query_search}, "\n";
  print "(".join(',', @{$q->{query_search_data}}).")\n";

  print $q->{query_limit}, "\n";
  print "(".join(',', @{$q->{query_limit_data}}).")\n";

yes I could just call the same function thrice with different parameters (that would make the library even more elegant), but at the moment I have good reasons to call it once, but I might change my mind in a near future.

  • query: is the query, with a first level filter
  • query_search: is the query with a first filter and second level search filter
  • query_limit: is the query with a first level filter and a second level search filter, and also a limit on the number of rows (useful for pagination)

Filters

There are two levels of filters, a main filter that is applied to all of the queries, and an additional search filter that is applied only to query_search and query_limit.

This could be useful because a main filter can be applied to a table, but the user can search for some records within the already filtered table.

For example, many users could have records in the same table:

ID User Song
1 1 AC-DC - Ride On.mp3
2 1 Metallica - Enter Sandman.mp3
3 2 The Sweet - Action.mp3
4 2 Sixx::AM - Life Is Beautiful.mp3
5 2 Metallica - Metal Militia.mp3

We can filter the previous table for each user, then the user can apply an additional filter:

my $columns = [
  { col => 'id', 'pk' => 1},
  { col => 'user', 'filter' => 1},
  { col => 'song', 'search' => 'Metallica', 'searchcriteria' => 'SUB'}
];

Global variable $dbh

The name of my project is "Sentosa AutoForms", and it is going to be driven by a SQL database. I'm using SQLite but it can be changed at a later time to MySQL or Postgresql.

Let's create my new project:

poet new Sentosa

cd Sentosa

and let's create our SQLite database. The schema goes on the db/schema.sql file:

create table if not exists af_info (
      id integer primary key autoincrement,
      attribute string not null,
      value string not null
    );

insert into af_info (attribute, value) values
('name', 'Sentosa AutoForms'),
('version', '0.01');

and the actual database data goes to data/sentosa.db:

sqlite3 -batch data/sentosa.db < db/schema.sql

This is how I would access this DB with a standard Perl application (using the DBI module):

{% raw %}
#!/usr/bin/perl

use strict;
use warnings;
use DBI;

my $dbh = DBI->connect(          
    'dbi:SQLite:dbname=../data/sentosa.db',
    '',                          
    '',                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare('SELECT value FROM af_info WHERE attribute="name"');
$sth->execute();

my $name = $sth->fetch();

print @$name;
print "\n";

$sth->finish();
$dbh->disconnect();
{% endraw %}

and this is how I am connecting to it in my web application:

{% raw %}
package Sentosa::Import;
use Poet::Moose;
extends 'Poet::Import';

use DBI;

method provide_var_dbh ($caller) {
  return DBI->connect(          
    'dbi:SQLite:dbname=data/sentosa.db',
    '',                          
    '',                          
    { RaiseError => 1 },         
  ) or die $DBI::errstr;
}
1;
{% endraw %}

and this is how I'm using it on my component index.mc:

{% raw %}
<%class>
use Poet qw($dbh);
</%class>
<% $.title %>
<%init>
my $sth = $dbh->prepare('SELECT value FROM af_info WHERE attribute="name"');
$sth->execute();

my $name = $sth->fetch();

$.title("Welcome to @$name");
</%init>
{% endraw %}

True Global Variable for Mason in Poet

A few days ago, while I was roaming around Singapore, I offered a bounty (twice) on an already existing question on StackOverflow Global Variable mason2 in Poet where the poster was asking how to use global variables in Mason. And I got a very nice and useful answer :)

Here I am describing how to get a true global and persistent variable. The content of a true global and persistent variable is preserved between requests.

Has your web server 10 different threads that answer HTTP requests? Then you'll end up having 10 different variables. Has your web server 1000 threads? Well... you got the idea!

But what's the proper use of such global variables? Any thread can potentially serve any request, so we cannot predict which server is going to answer which request. Private informations doesn't have to go here.

But most web applications are driven by some datatabase, and connecting to a database has cost in terms of performance. Since the database won't (usually) change between requests why do we have to establish the same database connection over and over again?

Actually, we don't have to: we can define a global variable $dbh whose value is a valid database handler, which will be preserved between requests. This database handler value will born with the thread and will die with the thread!

Oh but we still have to be careful since the handler could have been valid at the time of the creation of the thread, but then something bad could have happened (e.g. a timeout could have occoured, or some evil DBA might had killed your connection, just for the sake of it). Somehow we'll have to manage this.

Okay now the fun part. Please read Poet::Import and the awarded answer which I'm going to use as a reference, and then let's do some coding!

Global Variable Example

# generate app Myapp
poet new Myapp
cd Myapp

add a class Myapp::Import, here's where we have to define our global variables:

vi lib/Myapp/Import.pm

and then let's add some code:

package Myapp::Import;
use Poet::Moose;
extends 'Poet::Import';

# create some variable
has 'mytemp' => (is => 'ro', default => 'my temp value');

method provide_var_mytemp ($caller) {
    return $self->mytemp;
}
1;

then we can test our global variable in our components, let's print its value on comps/index.mc:

<%class>
use Poet qw($mytemp);
</%class>
I got this <% $mytemp %> variable.

Let's see how to store a database handler, on my next post!

Da HTML::Mason a Mason2... primi passi

Attenzione, questa pagina potrebbe contenere inesattezze: sono soltanto degli appunti su cui sto ancora lavorando!

Installazione Mason2

La nuova versione di HTML::Mason si chiama semplicemente Mason, ed è un sistema per la creazione di template e contenuti dinamici.

A differenza di HTML::Mason, la nuova versione si occupa di gestione pura dei template ed è stata resa indipendente dall'ambiente Web, la parte di integrazione con il server Web è invece che è realizzata dal framework Poet, tramite utilizza PSGI/Plack.

L'installazione di Poet si può fare in questo modo:

cpanm -S --notest Poet

e poiché Mason è un prerequisito, verrà installato anch'esso. cpanminus è un componente per la gestione di pacchetti presenti in CPAN.

Installiamo anche Mason::Plugin::PSGIHandler con il quale possiamo creare il nostro primo sito funzionante:

mkdir /var/wwwmason
mason_psgi_setup /var/wwwmason/firstapp/

ora per lanciare il nostro sito possiamo usare il seguente comando

cd /var/wwwmason/firstapp/; plackup -r

e il nostro applicativo sarà in ascolto su http://localhost:5000
se vogliamo configurare Apache per caricare il nostro sito, proviamo così:

<Location "/firstapp">

  SetHandler perl-script
  PerlResponseHandler Plack::Handler::Apache2
  PerlSetVar psgi_app  /var/wwwmason/firstapp/app.psgi


</Location>

attenzione che sembra convenga utilizzare plackup -r per lo sviluppo, mentre la soluzione su Apache va bene per i sistemi in produzione ma risulta difficoltoso ricaricare l'applicativo senza fare il restart di Apache: How do you deploy a PSGI script in Apache without restarting?

verificare anche i permessi della directory /var/wwwmason/firstapp/data dove Mason va a creare la cache ed i file precompilati.

Obtain name type and precision of all columns in a table using DBI

I had to obtain the structure (name, type, and precision for every column) of a table, connected via JDBC using DBI, and this is how I did it:

#!/usr/bin/perl

use strict;
use warnings;
use DBI;

my $host = '127.0.0.1';
my $url = 'jdbc:Cache://127.0.0.1/SOURCE';

my $dbo = DBI->connect("dbi:JDBC:hostname=$host;port=9001;url=$url", 'username', '***')
or die $DBI::errstr;

my $qry = $dbo->prepare("select * from custom.table");

$qry->execute();

print "Structure of $table \n\n";

my $num_fields = $qry->{NUM_OF_FIELDS};

for (my $i=0; $i< $num_fields; $i++) { 
  my $field = $qry->{NAME}->[$i];
  my $type = $qry->{TYPE}->[$i];
  my $precision = $qry->{PRECISION}->[$i];
  print "$field, $type, $precision\n";
}

$qry->finish();
$dbo->disconnect();

Programmatori pazzi

Bene o male, ma soprattutto male, qualunque sviluppatore impara a scrivere qualche piccola applicazione per il Web.

Magari abbiamo scritto la nostra applicazione per tenere traccia delle attività da svolgere, come pagare la bolletta del metano, ricordarsi di revisionare la macchina, portare Tobia alla toelettatura per cani, consegnare un nuovo prototipo al cliente, o pagare gli alimenti per la prole alla ex fidanzata. L'accesso all'applicativo sarà previa autenticazione, e gli utenti potranno inserire i propri dati, leggerli in tabella, ricercarli, filtrarli.

Poi che cosa manca? Gli utenti, oltre ad interagire con l'applicativo Web, vorranno scaricare i dati e portarli sul telefonino, farsene una copia su chiavetta, o importarli in Excel e farci qualche piccola elaborazione.

Da qualche parte nella pagina si dovrà predisporre un link o una paginetta di download, e gli utenti, dopo aver aggiustato qualche filtro, potranno scaricare il file sul proprio client e gestirlo come preferiscono.

E quindi, il programmatore medio che cosa si inventa? Generalmente la tecnica scelta si attesta intorno a queste soluzioni:

  1. si crea una bella crontab sul server, con una riga che viene eseguita ogni tot giorni, che crea una estrazione per ogni utente in \var\www\home\downloads\attivita-nomeutente.dat ;
  2. la pagina di download, quando viene richiamata, inizia a scrivere un file in \var\www\home\downloads\attivita-nomeutente.dat. Una volta competato il file, viene mandato in output un bel link all'estrazione appena creata;
  3. la pagina di download richiama un processo sul server che inizia a scrivere un file in \var\www\home\downloads\attivita-nomeutente.dat. Poichè il processo è asincrono, si mette uno sleep di 5 secondi in modo da dare il tempo al processo asincrono di completare il file. Poi ci mettiamo un bel link al file appena generato.

Notate qualche cosa di strano?

La prima tecnica è molto limitata: come possiamo applicare dei filtri? Ma uno sviluppatore creativo è in grado di risolvere anche questo: in fondo, quanto ci vuole a creare in anticipo tutti i file con tutti i filtri possibili ed immaginabili, tutti già preimpostati?
Per la seconda tecnica, uno script con privilegi di scrittura su di una cartella che poi verrà servita tramite server HTTP è il sogno di ogni criminale informatico. Ma se proprio non ci sono altre possibilità, conviene almeno sapere che oltre al chmod 777 o all'Everyone Full Control esistono anche svariate vie di mezzo.
Per la terza ogni commento è superfluo. Posso solo giurare di aver visto anche questo!

Se però non avete notato altro, forse la sicurezza informatica non è il vostro mestiere, e probabilmente non lo sarà mai.

Il vero problema, grosso come un pilone portante del Burj Khalifa di Dubai, è che se mettiamo un file in \var\www\home\downloads_ l'utente vi potrà accedere felicemente andando al link _http://www.example.com/downloads/attivita-nomeutente.dat ma oltre al nostro amico autorizzato vi potrà entrare anche tutto il resto del mondo. Certo, disabilitare il browsing delle directory è senz'altro un'idea brillante, ma non basta.

Il concetto è che al nostro Web Server non importa assolutamente nulla dell'autenticazione che abbiamo sviluppato nel nostro programma: abbiamo implementato un sistema di autenticazione per le nostre paginette Web? Bene. Abbiamo utilizzato un framework che implementa la parte di sicurezza? Meglio ancora. Ma questo sistema lo dobbiamo implementare anche per i nostri file. Solo allora possiamo servire le nostre pagine, dinamicamente, e protette dalle nostre credenziali.

(Ci sono casi in cui generare precedentemente i file da far scaricare agli utenti è cosa buona e giusta; tra l'altro il Web Server è sicuramente molto più veloce del nostro script nel servire le pagine; in questi casi però cerchiamo almeno di dare ai file un indirizzo URL casuale).

Sviluppare per il Web 2.0?

Ho iniziato a sviluppare applicativi Web aziendali, pubblicati sulla Intranet, fin dal 1998.
Al tempo l'azienda non era nemmeno collegata ad Internet, per cui non avevo ancora a disposizione una casella di posta elettronica aziendale, e le linee di collegamento tra le varie sedi distaccate erano decisamente molto lente.

La piattaforma per cui sviluppavo era Windows NT, con Internet Information Server, e gli applicativi erano realizzati con tecnologia ASP in linguaggio VBScript. Il database era spesso un Microsoft Access (dite quello che volete, ma si tratta di un prodotto miracoloso!) o, quando eravamo fortunati, un SQL Server.

Dopo pochi anni sono passato allo sviluppo per Apache su Linux, ma ho accuratamente scelto di evitare il PHP. So che il PHP è molto amato dalle comunità di sviluppo, ma quando si impara a conoscere un prodotto come ASP, passare al PHP sembra fare salto indietro di secoli. In ASP c'era un solo RecordSet, lo stesso RecordSet di Access, lo stesso di SQL Server, lo stesso di qualsiasi altro database di sistema. Non un RecordSet simile con le stesse funzioni: era proprio la stessa libreria di sistema: di per se, ASP era un sistema molto scarno, ma che si appoggiava largamente alle librerie di Windows.

Nel mondo PHP invece ogni diverso database aveva del codice diverso per essere gestito. La connessione ad un MySql era diversa dalla connessione ad un PostgreSQL, diversa dalla connessione ad un ODBC, diversa la connessione da una qualsiasi altra fonte dati. Diversa da qualsiasi connessione a qualsiasi database sviluppato per la console di Linux o per qualche applicativo client server.

(A vete intuito per quale ragione nel mondo Windows si parla di DLL Hell, mentre nel mondo Linux no? Ho il sospetto che nel mondo Linux vada di moda la riscrittura del codice piuttosto che la condivisione delle librerie... nonostante questo, avrei comunque qualche bella storia di .so Hell da raccontare!)

Per cui ho deciso che lo strumento per cui avrei sviluppato sarebbe stato il linguaggio Perl.
Non si tratta di un linguaggio per puristi, ma in fondo chi se ne importa? Ho programmato in qualsiasi cosa mi sia capitata per le mani, e l'ho trovato fin da subito un prodotto molto efficiente. E pazienza che la sintassi fosse orrenda e poco coerente: una volta abituati al VBScript, non si può che migliorare!

La cosa che mi ha convito fin subito del Perl era la sua immensa collezione di librerie, per connettersi o per gestire qualsiasi cosa (database, periferiche, API di sistema, c'era addirittura del codice per gestire il proprio account MySpace, il tutto via codice...!). Generalmente le librerie del Perl erano di buona qualità e progettate con buon gusto. Inoltre, da non trascurare il fatto che la stessa libreria che si utilizzava per sviluppare applicativi di sistema era la stessa libreria da utilizzare per lo sviluppo Web: avevo finalmente trovato la coerenza anche nel mondo Linux!

Ma soprattutto avevo trovato il miglior framework per il Web esistente sulla terra: HTML::Mason!
Il suo concetto di ereditarietà era favoloso: potevi concentrarti nello scrivere una paginetta che mostrava il contenuto della pagina, e tutto il resto, dal vestito grafico, alle connessioni al database, alle autorizzazioni necessarie, venivano ereditate secondo uno schema deciso in fase di progettazione.

Purtroppo si trattava di un sistema molto difficile da installare e da configurare (non vi sto a raccontare di tutti i conflitti di librerie di sistema per riuscire a farlo funzionare... quando inizi ad utilizzare componenti che non sono presenti in una distribuzione e sono scarsamente utilizzati, anche il mondo dei pacchetti e delle dipendenze mostra i suoi limiti) ma una volta predisposto il tutto diventava uno strumento davvero veloce per sviluppare applicazioni Web complesse.

L'ultimo applicativo che ho sviluppato era una specie di Cartella Clinica, con dati dei pazienti, variazioni anagrafiche, esami di laboratorio, esami radiologici, prescrizioni farmaceutiche, il tutto visualizzabile via Web oppure scaricabile in formato leggibile dal software del medico, previo accesso tramite utente e password.

Le fonti di dati erano MySql, Oracle, SQL Server (eh sì, vi si poteva accedere anche da Linux!), o file di testo importati tramite crontab.
Le pagine erano realizzate in XHTML, con CSS, con qualche bello spunto preso da CSS Zen Garden. JavaScript, come era giusto fare al tempo, era ridotto al minimo e si limitava a pre-convalidare i Form.

Poi si sa come vanno le cose nella pubblica amministrazione. Lo sviluppo interno viene abbandonato, e viene appaltato all'esterno. Così il mio lavoro non consiste più nel progettare e realizzare programmi, ma consiste nel riuscire a far funzionare programmi scritti da terzi, con tecniche di programmazione arcaiche e/o discutibili, e che ~~probabilmente~~ sicuramente avrei potuto scrivere meglio!

Negli ultimi cinque anni il mondo del Web è cambiato, il nuovo Web si chiama Web 2.0.
Cosa bisogna imparare per restare al passo?
Ho scelto di utilizzare il Google App Engine, questo richiede di imparare a programmare in Python ma (database a parte) tutto ciò che si utilizzava sotto Apache è ancora molto attuale. E all'autenticazione, il lato debole di ogni applicativo Web, non ci pensa più il programmatore ma se ne occupa Google!
La lista seguente serve soprattutto a me, per capire che cosa è cambiato dal punto in cui ero rimasto. Non è definitiva e la aggiornerò quando serve:

  • XHTML è stato abbandonato. Il successore di HTML4.01 è HTML5
  • Un applicativo, per essere funzionale ed apparire moderno, deve utilizzare AJAX
  • Utilizzare JavaScript non è più un tabù!
  • Creare a mano il proprio JavaScript è tabù: dove possibile, utilizzare JQuery o altre librerie standard!
  • Il framework standard di Google App Engine è Django. Non so se sarà il mio preferito, forse dovrei approfondire Pylons (che deriva in qualche modo da HTML::Mason)
  • Non bisogna dimenticarsi di integrare il proprio applicativo con Facebook e altri social networks, come ad esempio Google+.

Happy coding!