Choosing a Modern C++ stack

I'm starting a new project in C++, but I've run into a pair of questions before start:

  1. Which build system should I use?
  2. Which unit testing framework?

Choosing Build System (Meson)

I have used before Make, Maven, Scons, Gradle and Autotools.

But I have some reasons to try find something else.

Autotools
It is not easy to configure and maintain. There are several configuration files and several configuration steps.
Gradle
C++ feature is still incubating. Not very fast. You can check a similar example project at Build C++ project with Gradle.
Make
I don't love the syntax. Files tends to get messy as project grows.
Scons
It is just slow.
Maven
It is slow and you might end up "Javatizing" your C++ project structure.

Note

I've listed just things I don't like, those projects have other great features.

Now I'm considering Meson or CMake.

CMake has a big advantage over Meson, it is mature and widely used in many projects, which means there are many examples and it will fulfill your C++ project building needs.

Meson is a young project compared with CMake, but it is growing quite fast and it has been adopted in other big projects like Gnome, they have an initiative to port from Autotools to Meson.

I've chosen Meson because:

  • Syntax is really clear to me, when I read meson.build file I can quickly understand what is happening during build process.
  • It is fast. Altought it is written in Python, it generates a Ninja build project. First time you configure the project you has to run Meson, but for building or testing you are actually running Ninja.
$ meson build . # first time you configure the project
$ cd build
$ ninja build   # each time you build it
$ ninja test    # each time you run tests

I've found two interesting comparisons about available C++ build systems, they might be a little be biased because those comparisons come from Meson and Scons.

Unit Testing Framework

I have used some xUnit based libraries like UnitTest++, CppUTest or Google Test which match perfectly with Google Mock. If you want a safe bet that fulfills almost of your testing needs I highly recommend Google Test.

But time ago I found a testing framework with some interesting features, Catch:

  • It is just a header file with no external dependencies, so very easy to start (wget + include downloaded file).
  • You can use normal unit test style or BDD-style

If you want to know more about Catch, I recommend you to give it a try, it is a matter of 2 minutes to have a simple example up and running. You can also read some interesting articles like Why do we need yet another C++ test framework? or Testing C++ With A New Catch.

doctest: A Catch alternative

There is another testing framework named doctest, with same benefits as Catch, but it promises to be faster and lighter (benchmark results) than Catch.

doctest is modeled after Catch and some parts of the code have been taken directly, but there are differences.

It hasn't been easy to decide, both are really similar, you can check here differences between project using doctest and project using Catch.

I've finally chosen doctest because it promises to be faster: benchmark results.

Note

I've created project using both frameworks you can find them in corresponding branches: doctest branch or catch branch.

Hint

You can see diferencies between projects at: https://github.com/carlosvin/uuid-cpp/pull/1

Example

I've created an example to illustrate this article: https://github.com/carlosvin/uuid-cpp.

It is a basic implementation of UUID pseudo-random generator based on mt19937 which is not cryptographically secure.

Project output artifacts

  • Shared library: libuuid.
  • Header library for developers who want to use the shared library: include/Uuid.h.
  • Executable uuidgen (UUID generator).
  • Test executable (not installed). It tests shared library.

For example, if you execute ninja install on Linux, you will get something like:

/usr/local/lib/libuuid.so
/usr/local/include/Uuid.h
/usr/local/bin/uuidgen

Project structure (Fork project)

  • meson.build

    Root project file configuration. It defines project properties and subdirectories.

    project(
        'cpp-meson-example', # project name
        'cpp', # C++ project, e.g: for C project
        version : '1.0.0',
        license : 'MIT',
        default_options : ['cpp_std=c++11']) # compile for C++
    
    # it will be referred from subdir projects
    inc = include_directories('include')
    
    # meson will try to find a meson.build file inside following directories
    subdir('include')
    subdir('src')
    subdir('test')
    
  • include
    • meson.build

      Subdirectory build configuration file.

      # Select header files to be installed
      install_headers('Uuid.h')
      
    • Uuid.h

      Header file, it is the library interface definition which will be included from projects using that library

      namespace ids {
      
      class Uuid {
          private:
          // ...
      
  • src
    • meson.build (src)

      It declares 2 output artifacts libuuid and uuidgen.

      libuuid = shared_library(
          'uuid', # library name
          'Uuid.cpp', # source files to be compile
          include_directories : inc, # previously declared include directories in root :code:`meson.build`
          install : true) # :code:`libuuid` will be part of project installation
      
      uuidgen = executable(
          'uuidgen', # executable name
          'main.cpp', # source files to compile
          include_directories : inc, # previously declared include directories in root :code:`meson.build`
          link_with : libuuid, # linking executable with shared previously declared shared library :code:`libuuid`
          install : true) # :code:`uuidgen` executable be part of project installation
      
    • main.cpp

      Entry point for main executable uuidgen

      #include "Uuid.h"
      #include <iostream>
      
      int main()
      {
          ids::Uuid uuid;
          std::cout << uuid.to_str() << std::endl;
          return 0;
      }
      
    • Uuid.cpp

      Implementation of declared class in header file.

      #include "Uuid.h"
      
      Uuid::Uuid()
      { // ...
      
  • test
    • meson.build (test)

      File to configure tests build process.

      testexe = executable(
          'testexe', # test executable name
          'uuid_test.cpp', # tests source files to be compiled
          include_directories : inc,  # declared include directories in root :code:`meson.build`
          link_with : libuuid) # link test executable with previously declared shared library :code:`libuuid`
      
      # test execution
      test('Uuid test', testexe)
      
      # we can specify other test execution passing arguments or environment variables
      test('Uuid test with args and env', testexe, args : ['arg1', 'arg2'], env : ['FOO=bar'])
      
    • doctest.h

      doctest library in a single header file. You can try to automate library installation as part of your build process, but I haven't figure out yet a way to do it with Meson. For now I've installed it manually:

      cd test
      wget https://raw.githubusercontent.com/onqtam/doctest/master/doctest/doctest.h
      
    • uuid_test.cpp

      Tests implementation.

       // This tells doctest to provide a main() - only do this in one cpp file
      #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
      
      #include "doctest.h"
      #include "Uuid.h"
      #include <string>
      
      constexpr int MAX_ITERS = 100;
      
      TEST_CASE( "Uuid" ) {
          for (int i=0; i<MAX_ITERS; i++) {
              ids::Uuid uuid;
              std::string uuid_str {uuid.to_str()};
      
              MESSAGE(uuid_str);
              CHECK(uuid.most > 0);
              CHECK(uuid.least > 0);
              CHECK(uuid_str.size() == 36);
          }
      }
      
      // BDD style
      
      SCENARIO( "UUID creation" ) {
      
          GIVEN( "A random UUID " ) {
              ids::Uuid uuid;
              std::string uuid_str {uuid.to_str()};
      
              CHECK(uuid_str.size() == 36);
      
              WHEN( "get the most and least" ) {
                  THEN( "should be more than 0" ) {
                      CHECK( uuid.most > 0);
                      CHECK( uuid.least > 0);
                  }
              }
          }
      }
      

Sistema de Ficheros en C++17

Comenzar a utilizar característica experimental filesystem C++17 (g++)

Simplemente debemos "decir" al compilador que estamos escribiendo código C++17 (-c++1z) y que añada la librería estándar con la librería filesystem (-lstdc++fs).

g++ -std=c++1z main.cpp -lstdc++fs && ./a.out

Veamos un ejemplo muy simple utilizando la clase std::filesystem::path.

#include <experimental/filesystem>
#include <iostream>

namespace fs = std::experimental::filesystem;
using namespace std;

int main()
{
    fs::path aPath {"./path/to/file.txt"};

    cout << "Parent path: " << aPath.parent_path() << endl;
    cout << "Filename: " << aPath.filename() << endl;
    cout << "Extension: " << aPath.extension() << endl;

    return 0;
}

Compilar y ejecutar: ejemplo básico C++17

Como vemos el resultado de la ejecución es:

$ g++ -std=c++1z main.cpp -lstdc++fs && ./a.out
$ ./a.out

Parent path: "./path/to"
Filename: "file.txt"
Extension: ".txt"

Características de filesystem C++17

A continuación vamos a analizar algunas características que nos proporciona std::filesystem con ejemplos en C++11 y C++17, de esta forma podremos hacernos una idea de las utilidades que esta nueva librería nos trae y cómo efectivamente ayuda al desarrollador a escribir código más claro y seguro.

std::filesystem::path

Más arriba ya hemos visto un pequeño ejemplo de uso de clase std::filesystem::path. Ésta abstracción nos proporciona una ruta a ficheros y directorios multi-plataforma, utilizando el separador de directorios correspondiente a la plataforma en la que trabajamos \ en sistemas basados en Windows y / en sistemas basados en Unix.

Separador de directorios

Si quisiéramos que nuestro software utilizase el separador de directorios correcto para una plataforma, en C++11 podríamos utilizar una macro de compilación condicional:

#include <iostream>

using namespace std;

#ifdef _WIN32
const string SEP = "\\";
#else
const string SEP = "/";
#endif

int main()
{
    cout << "Separator in my system " << SEP << endl;
    return 0;
}

Compilar y ejecutar: ejemplo separador C++11

En C++17 sería algo más sencillo:

#include <experimental/filesystem>
#include <iostream>

namespace fs = std::experimental::filesystem;
using namespace std;

int main()
{
    cout << "Separator in my system " << fs::path::preferred_separator << endl;
    return 0;
}

Compilar y ejecutar: ejemplo separador C++17

Operador separador de directorios

std::filesystem::path implementa el operador /, el cual nos permite concatenar fácilmente rutas a ficheros o directorios.

Si quisiéramos construir rutas a directorios en C++11, tendríamos que implementar cierta lógica extra para detectar que no añadimos separadores extra y para utilizar el separador correcto:

#include <iostream>

using namespace std;

#ifdef _WIN32
const string SEP = "\\";
#else
const string SEP = "/";
#endif

int main()
{
    string root {"/"};
    string dir {"var/www/"};
    string index {"index.html"};

    string pathToIndex{};
    pathToIndex.append(root).append(SEP).append(dir).append(SEP).append(index);

    cout << pathToIndex << endl;
    return 0;
}

Compilar y ejecutar: ejemplo concatenar rutas C++11. Como vemos el resultado no es del todo correcto, deberíamos comprobar si las partes de la ruta ya contienen separador, para no añadirlo.

Toda esta lógica está ya implementada en std::filesystem::path, así que el código en C++17 sería algo así:

#include <experimental/filesystem>
#include <iostream>

namespace fs = std::experimental::filesystem;
using namespace std;

int main()
{
    fs::path root {"/"};
    fs::path dir {"var/www/"};
    fs::path index {"index.html"};

    fs::path pathToIndex = root / dir / index;

    cout << pathToIndex << endl;
    return 0;
}

Compilar y ejecutar: ejemplo concatenar rutas C++17. Aquí el código es más limpio y el resultado es simplemente correcto, no hay separadores duplicados.

Crear y borrar directorios

std::filesystem introduce algunas facilidades para crear y borrar directorios y ficheros, primero vamos a ver una de las formas de hacerlo en C++11.

#include <iostream>
#include <cstdio>
#include <sys/stat.h>

using namespace std;

int main()
{
    auto opts = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
    mkdir("sandbox", opts);
    mkdir("sandbox/a", opts);
    mkdir("sandbox/a/b", opts);
    mkdir("sandbox/c", opts);
    mkdir("sandbox/c/d", opts);

    system("ls -la sandbox/*");

    remove("sandbox/c/d");
    remove("sandbox/a/b");
    remove("sandbox/c");
    remove("sandbox/a");
    remove("sandbox");

    system("ls -la");

    return 0;
}

Compilar y ejecutar: crear y borrar directorios C++11. Para crear y borrar directorios anidados, debemos hacerlo uno por uno. Podemos escribir este fragmento de código con menos líneas, pero aún así tendremos que tener cuidado del orden en el que creamos/borramos los directorios.

En C++17 podemos borrar y crear directorios anidados con una sola llamada.

#include <experimental/filesystem>
#include <iostream>

namespace fs = std::experimental::filesystem;
using namespace std;

int main()
{
    fs::create_directories("sandbox/a/b");
    fs::create_directories("sandbox/c/d");
    system("ls -la sandbox/*");

    cout << "Were directories removed? " << fs::remove_all("sandbox") << endl;
    system("ls -la");

    return 0;
}

Compilar y ejecutar: crear y borrar directorios C++17.

Ejemplo completo: Iterar Recursivamente por Directorios

Vamos a ver un ejemplo algo más completo, consiste en iterar recursivamente a través de directorios, filtrando los ficheros por extension.

Este es el ejemplo en C++11, sin filtrar por extension, para evitar complicarlo:

recursive-directory/filesystem.11.cpp (Código fuente)

#include <dirent.h>
#include <cstring>
#include <iostream>
#include <fstream> // std::ofstream
#include <vector>
#include <memory>
#include <system_error>
#include <sys/stat.h>

using namespace std;

const string UP_DIR = "..";
const string CURRENT_DIR = ".";
const string SEP = "/";


string path(initializer_list<string> parts)
{
    string pathTmp {};
    string separator = "";
    for (auto & part: parts)
    {
        pathTmp.append(separator).append(part);
        separator = SEP;
    }
    return pathTmp;
}

vector<string> getDirectoryFiles(const string& dir, const vector<string> & extensions)
{
    vector<string> files;
    shared_ptr<DIR> directory_ptr(opendir(dir.c_str()), [](DIR* dir){ dir && closedir(dir); });
    if (!directory_ptr)
    {
        throw system_error(error_code(errno, system_category()), "Error opening : " + dir);
    }

    struct dirent *dirent_ptr;
    while ((dirent_ptr = readdir(directory_ptr.get())) != nullptr)
    {
        const string fileName {dirent_ptr->d_name};
        if (dirent_ptr->d_type == DT_DIR)
        {
            if (CURRENT_DIR != fileName && UP_DIR != fileName)
            {
                auto subFiles = getDirectoryFiles(path({dir, fileName}), extensions);
                files.insert(end(files), begin(subFiles), end(subFiles));
            }
        }
        else if (dirent_ptr->d_type == DT_REG)
        {
            // here we should check also if filename has an extension in extensions vector
            files.push_back(path({dir, fileName}));
        }
    }
    return files;
}

int main ()
{
    auto opt = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
    mkdir("sandbox", opt);
    mkdir("sandbox/a", opt);
    mkdir("sandbox/a/b", opt);

        vector<string> e_files = {
            "./sandbox/a/b/file1.rst",
            "./sandbox/a/b/file1.txt",
            "./sandbox/a/file2.RST",
            "./sandbox/file3.md",
            "./sandbox/will_be.ignored"
        };

        // create files
        for (auto &f: e_files)
        {
                ofstream of(f, ofstream::out);
                of << "test";
        }

    cout << "filtered files: " << endl;
        for (auto &f: getDirectoryFiles(".", {".rst", ".RST", ".md"})){
            cout << "\t" << f << endl;
        }

    return 0;
}

Compilar y ejecutar el ejemplo C++11.

El siguiente ejemplo filtra los ficheros por extension.

recursive-directory/filesystem.17.cpp (Código fuente)

#include <experimental/filesystem>
#include <iostream>
#include <vector>
#include <fstream>
#include <algorithm>    // std::find

namespace fs = std::experimental::filesystem;
using namespace std;

vector<string> getDirectoryFiles(const string & dir, const vector<string> & extensions)
{
        vector<string> files;
    for(auto & p: fs::recursive_directory_iterator(dir))
    {
        if (fs::is_regular_file(p))
        {
                if (extensions.empty() || find(extensions.begin(), extensions.end(), p.path().extension().string()) != extensions.end())
            {
                files.push_back(p.path().string());
            }
        }
    }
    return files;
}

int main()
{
    fs::create_directories("sandbox/a/b");
        vector<string> e_files = {
            "./sandbox/a/b/file1.rst",
            "./sandbox/a/b/file1.txt",
            "./sandbox/a/file2.RST",
            "./sandbox/file3.md",
            "./sandbox/will_be.ignored"
        };

        // create files
        for (auto &f: e_files)
        {
                ofstream(f) << "test";
        }

    cout << "filtered files: " << endl;
        for (auto &f: getDirectoryFiles(".", {".rst", ".RST", ".md"})){
            cout << "\t" << f << endl;
        }

    return 0;
}

Compilar y ejecutar el ejemplo C++17.

Contenedores Docker multi-dominio

Caso de Uso

Tenemos varias aplicaciones servidoras a la vez en un mismo entorno de desarrollo, cada una encapsulada en un contenedor, llamémosles de ahora en adelante "Contenedor A" y "Contenedor B".

Utilizando docker estas aplicaciones tienen la misma dirección IP en nuestra máquina, una forma de distinguirlas es cambiando el puerto que exponen.

/galleries/docker-multidomain/ip.thumbnail.png

Aplicaciones exponiendo la misma dirección IP utilizando diferentes puertos para diferenciar las aplicaciones

Pero esto es un poco confuso, ¿8080 sigfica que accedemos a las "aplicación A" y 8081 significa "aplicación B"?

Sería mucho más sencillo de recordar algo así:

/galleries/docker-multidomain/domain.thumbnail.png

Diferenciando aplicaciones por nombre de dominio

Obtener este valor semántico extra es más sencillo de lo que parece.

Cómo configurar un Proxy Inverso Multi-Dominio

Dije que era fácil porque no vamos a tener que hacer casi nada, ya que otro contenedor hará casi todo el trabajo por nosotros. Vamos a utilizar nginx-proxy, que generará automáticamente las configuraciones necesarias para NGINX.

Nota

Puedes descargar el ejemplo completo desde: https://github.com/carlosvin/docker-reverse-proxy-multi-domain

Así que al final no tendremos 2 contenedores, sino también tendremos un tercero que hará las veces de proxy.

/galleries/docker-multidomain/proxy.thumbnail.png

Los 2 contenedores y el proxy

Estructura del proyecto de ejemplo

  • docker-compose.yaml (Archivo con la configuración descrita en la imágen previa)
  • a (Directorio para la aplicación A)
    • Dockerfile (Archivo con la configuración del contenedor A)
  • b (Directorio para la aplicación B)
    • Dockerfile (Archivo con la configuración del contenedor B)

Ver proyecto.

Configuración de la arquitectura (docker-compose)

La parte más importante es la configuración de las relaciones entre los contenedores.

docker-reverse-proxy-multi-domain/docker-compose.yaml (Código fuente)

a:
  build: a
  environment:
    VIRTUAL_HOST: a.domain.com
  restart: always

b:
  build: b
  environment:
    VIRTUAL_HOST:  b.domain.com
  restart: always

nginx-proxy:
  image: jwilder/nginx-proxy
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - /var/run/docker.sock:/tmp/docker.sock:ro

  restart: always
  privileged: true
  • En la línea 4 y 10 es donde configuramos el nombre de dominio que queremos utilizar para cada aplicación.
  • A partir de la línea 13 configuramos el proxy (esta es la parte de copiar y pegar).
  • En la línea 2 y 8 estamos indicando a docker-compose que tiene que construir las imágenes dentro del directorio indicado. Por ejemplo, en la línea 2, estamos indicando que docker-compose tiene que construir la imágen docker utilizando ./a/Dockerfile.

Configuración de la imágen de la aplicación

A continuación vamos a comentar la configuración de la imágen del contenedor para la aplicación A.

docker-reverse-proxy-multi-domain/a/Dockerfile (Código fuente)

FROM httpd:2.4
RUN echo "<html><body><h1>A</h1>App A works!</body></html>" > /usr/local/apache2/htdocs/index.html

Línea 1: Importamos una imágen con un servidor apache.

Línea 2: Servimos un archivo que muestra "Host A" como página por defecto.

La configuración para la aplicación B, es prácticamente la misma:

docker-reverse-proxy-multi-domain/b/Dockerfile (Código fuente)

FROM httpd:2.4
RUN echo "<html><body><h1>B</h1>App B works!</body></html>" > /usr/local/apache2/htdocs/index.html

Añadiendo los nombres de dominio a tu configuración

En Linux simplemente tenemos mapear la dirección local a los nombres de dominio que hayas elegido, en nuestro ejemplo es a.domain.com y b.domain.com.

1
2
3
4
5
#/etc/hosts
127.0.0.1             localhost.localdomain localhost
::1                 localhost6.localdomain6 localhost6
127.0.0.1   a.domain.com
127.0.0.1   b.domain.com

Simplemente he añadido las líneas 4 y 5.

¡Todo listo!

Ya solo nos queda probar el ejemplo.

docker-compose build
docker-compose up

Ya están las tres contenedores arrancados.

Ahora podemos abrir nuestro navegador y escribir a.domain.com y nos mostrará el texto App A works!. Si escribimos b.domain.com entonces veremos App B works!.

/galleries/docker-multidomain/a.screenshot.thumbnail.png

a.domain.com en el navegador

/galleries/docker-multidomain/b.screenshot.thumbnail.png

b.domain.com en el navegador

Nota

En la mayoría de distribuciones Linux necesitarás privilegios para ejecutar los comandos docker (sudo).

Comparación frameworks REST para RUST

Estoy empezando a hacer mis primeros pinitos con Rust, es un lenguage de programación que promete ser tan rápido como C/C++, pero más seguro en cuanto a la gestión de memoria ser refiere. Básicamente, no permite al programador hacer "cosas malas" con la memoria como: olvidarse de liberar la memoria que no se va a utilizar o liberar memoria que no es tuya. En estos casos, Rust simplemente no compilará.

Para aprender he pensado en hacer algún pequeño proyecto, tengo algunas ideas, pero todas pasan por ofrecer una API REST, así que me he puesto a buscar frameworks que me faciliten esta tarea y me he llevado una grata sorpresa al encontrar una muy buena comparativa con el estado de los frameworks REST para Rust, a continuación os dejo el enlace:

https://github.com/flosse/rust-web-framework-comparison

Convertir archivos en formato Windows a formato Unix

Si alguna vez has programado desde un entorno Windows para entornos Unix, seguramente habrás tenido este problema: Los archivos que instalas en tu entorno Unix tienen formato Windows.

Hay un forma bastante sencilla de convertir todos los archivos que tienes en un directorio de formato Windows a Unix.

find . -type f -print0 | xargs -0 dos2unix

Lo he sacado, como no, de http://stackoverflow.com/questions/11929461/how-can-i-run-dos2unix-on-an-entire-directory

Creando REST URLs

Las primeras veces que me puse a diseñar una API REST cometí unos cuantos errores, por supuesto. A continuación os voy a contar algunos de los errores que cometí y lo que he entendido hasta hoy sobre la construcción de URLs REST con ejemplos.

Fundamentos REST

  • Utilizamos URLs para acceder a recursos.
  • Utilizamos verbos para modificar recursos.
  • Nuestros verbos están proporcionados por el protocolo HTTP.
  • Los verbos tienen un equivalente directo con las operaciones CRUD [1].
  • Para acceder a un recurso existente necesitamos su identificador.

Verbos REST

POST
(C) Utilizado para crear nuevos recursos.
GET
(R) Utilizado para leer un recursos existentes en el sistema.
PUT
(U) Utilizado para actualizar recursos existentes.
DELETE
(D) Utilizado para borrar recursos existentes.

En una tabla quedará más claro

Verbo REST Acción CRUD Debe exisitir el recurso
POST Crear No
GET Leer
PUT Actualizar
DELETE Borrar

Acceso a Recursos

Un recurso es a lo que quieres acceder. Por ejemplo, un coche.

Para poder acceder a un coche no es suficiente con esta información, no puedes ir a un concesionario y preguntar por un coche en general, tienes que decir qué coche quieres. Así que llegas al concesionario y dices:

Hola, buenos días. Quiero información sobre el Fiat Bravo 1.9 Emotion 120CV.

De esta forma el dependiente sabe cuál es y te puede dar la información.

"Fiat Bravo 1.9 Emotion 120CV" es nuestro identificador.

Trasladando el ejemplo a las APIs REST:

GET   http://tiendadecoches.es/api/coches/fiat-bravo-19-emotion-120cv

De esta forma nuestra API nos puede proporcionar información del coche.

Esto es un ejemplo muy simplificado, pero realmente cuando accedemos a un recurso concreto solemos utilizar algo que lo identifique de forma unívoca. Una práctica común y recomendable es utilizar UUID.

GET  http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f

Pero nuestra API, al igual que una tienda, no tiene por qué ser tan estricta, podemos preguntar por los coches que tienen ciertas caracteríticas, podemos ir al concesionario y decir:

Hola buenos días, quiero un Fiat Bravo.

Y el dependiente, amablemente, te mostrará todos los Fiat Bravo que tiene. Veamos cómo podemos decir esto a nuestra API.

GET  http://tiendadecoches.es/api/coches/?marca=fiat&modelo=bravo

Nuestra API nos devolverá todos los coches que son marca Fiat y modelo Bravo.

Marca y modelo en este caso son lo que llamamos parámetros de consulta (query parameters).

Como os habréis dado cuenta durante el ejemplo, para obtener información de un recurso siempre hemos utilizado el verbo GET.

Modificar Recursos

Nuestra API también nos puede permitir modificar un recurso, al igual que para pedir información, para modificar un recurso necesitamos especificar qué recurso queremos modificar, así que necesitamos otra vez un identificador.

Antes queríamos información (leer) y utilizábamos nuestro verbo GET, ahora lo único que cambia es el verbo, queremos modificar así que utilizamos el verbo equivalente que nos proporciona el protocolo HTTP: PUT.

PUT   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f

Pero nos falta algo, tenemos que decir qué queremos cambiar del coche, por ejemplo, imaginemos que queremos cambiar la cilintrada y poner 100CV.

Tenemos que enviar la nueva cilindrada a esta URL http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f por HTTP utilizando el verbo PUT.

El protocolo HTTP nos permite enviar información en un mensaje PUT, así que solo nos falta pensar en el formato en que lo vamos a enviar.

Podemos enviarlo en JSON o XML o como queramos, solo tenemos que estar seguros de que el formato que enviamos es lo que espera recibir el servidor.

Nota

Cuando definimos una API REST tenemos que definir también el formato en que vamos a enviar los datos.

Ejemplo en JSON:

{ cilindrada: 100 }

Borrar Recursos

Continuando con el ejemplo de los coches, imaginemos que ahora somos el concesionario, y que ya no queremos vender más ese Fiat Bravo (concretamente el cce05bee-386b-11e5-a151-feff819cdc9f). Seguiremos manteniendo la URL que identifica el recurso, pero cambiamos el verbo, no queremos leer (GET), ni modificar (PUT), queremos borrar (DELETE).

DELETE   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f

En el caso del borrado, no hay que proporcionar ninguna información adicional, con el verbo (DELETE) y el recurso es suficiente.

Crear Recursos

Y nos queda último verbo, crear (POST). En este caso no hay que identificar el recurso, porque no existe todavía.

POST   http://tiendadecoches.es/api/coches/

Lo que sí que tenemos que enviar son los datos del recurso que vamos a crear.

En nuestro ejemplo, queremos crear un coche, así que ponemos toda la información necesaria para crear un coche dentro de la llamada POST de HTTP, algo muy parecido como hemos hecho en el apartado Modificar Recursos, pero en este caso mandamos toda la información necesaria, no solo la cilindrada.

Ejemplo en JSON:

{
"marca": "Fiat",
"modelo": "Bravo"
"anio": 2010
"puertas": 5,
"cilindrada": 120,
"version": "Emotion",
"climatizador": true,
"ac": false,
"fuel": "Diesel"
}

Podemos delegar en el sistema, para que cuando pidamos la creación de un recurso nuevo, nos asigne un nuevo identificador, o simplemente enviarlo con el resto de información:

{
"identificador": "cce05bee-386b-11e5-a151-feff819cdc9f"
"marca": "Fiat",
"modelo": "Bravo"
"anio": 2010
"puertas": 5,
"cilindrada": 120,
"version": "Emotion",
"climatizador": true,
"ac": false,
"fuel": "Diesel"
}

Colecciones

Algo que no quiero pasar por alto, porque al menos para mí no fue obvio, es el manejo de colecciones. Realmente ya está explicado, porque todas las acciones que hemos visto previamente sobre los coches, estaba aplicando realmente a una colección de coches.

Pero, ¿qué pasa si un recurso tiene a su vez una colección? Siguiendo con los coches, un coche puede tener una lista de aceites con los que puede funcionar, así que nuestra API debería permitir obtener, modificar, borrar o crear elementos en la lista.

Nota

Para el ejemplo asumiremos que el identificador del aceite es el atributo tipo.

Añadir un elemento a la colección

Si queremos añadir un elemento a la colección de coches lo que vamos a hacer es crear un nuevo coche, así que estamos en el caso de Crear Recursos.

Para añadir un nuevo aceite al coche cce05bee-386b-11e5-a151-feff819cdc9f, que ya existe:

POST   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/

{
"tipo": "5W30",
"otros_datos": "este es el mejor del mundo para este coche"
}

Si queremos añadir otro aceite:

POST   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/

{
"tipo": "10W30",
"otros_datos": "otras cosas sobre aceites de coche",
}

Modificar un elemento de la colección

Si queremos modificar los datos del aceite 5W30 del coche cce05bee-386b-11e5-a151-feff819cdc9f:

PUT   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/5W30/

{
"tipo": "5W30",
"otros_datos": "este ya no es el mejor del mundo para este coche"
}

Borrar un elemento de la colección

Para borrar un aceite 10W30 del coche cce05bee-386b-11e5-a151-feff819cdc9f:

DELETE   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/10W30

Leer un elemento de la colección

Para obtener la información del aceite 10W30 del coche cce05bee-386b-11e5-a151-feff819cdc9f:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/10W30

Listar elementos de la colección

Como hemos visto en Leer un elemento de la colección, podemos obtener información de cualquier elemento de la colección, pero también podemos obtener varios elementos de la colección, ordenarlos, paginarlos y aplicar cualquier tipo de acciones típicas de una colección.

Podemos obtener todos los aceites soportados por el coche cce05bee-386b-11e5-a151-feff819cdc9f, es tan simple como:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/

Pero también podemos proporcionar otras funcionalidades en nuestra API, como obtener los resultados ordenados:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?ordenar_por=tipo&orden=ascendente

Podemos pedir al API que nos devuelva los 10 primeros aceites del coche cce05bee-386b-11e5-a151-feff819cdc9f:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?numero_de_elementos=10

Cuando no queremos mostrar toda la lista completa, podemos proporcionar un sistema de paginación:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?pagina=3&numero_de_elementos=3

En la petición de arriba, estamos diciendo que nos devuelva la página 3 de los aceites del coche cce05bee-386b-11e5-a151-feff819cdc9f y que nos muestre 3 aceites por página. Si quisiéramos ir a la página siguiente:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?pagina=4&numero_de_elementos=3

Todas estas funcionalidades, son posibles gracias a los parámetros de consulta.

Típico error

La primera vez que intenté diseñar un API REST lo que hice fué otra cosa, era una API, pero no REST.

Mi principal error fue en la construción de las URLs, incluí verbos sin tener en cuenta que los verbos ya me los proporcionaba el protocolo HTTP.

Por ejemplo, creaba URLs del tipo:

POST    http://example.com/api/coches/seat-ibiza/borrar-rueda/3

Cuando lo correcto sería

DELETE  http://example.com/api/coches/seat-ibiza/ruedas/3

Video Tutoriales

Estos tutoriales me fueron de gran ayuda y os recomiendo que los veáis enteros:

[1] Create, Read, Update, Delete

Gestión de dependencias C++: Biicode

Hace tiempo que estoy interesado la construcción, gestión de dependencias, empaquetado y despliegue de proyectos software. Para Java, Scala, Python, etc. Es bastante sencillo ya que hay herramientas como Maven, Gradle, pip, Sbt, etc. Pero para C++_, lo más parecido que he encontrado es Maven con el plugin Nar o Gradle con su plugin cpp (que está en desarrollo).

Hace tiempo que conozco Biicode, pero no había sacado nunca tiempo para probarlo, hasta hoy.

El funcionamiento general es muy sencillo.

Necesitamos instalar biicode en nuestra máquina.

Yo he creado un pequeño ejemplo utilizando el sistema de log de la librería Poco.

He ejecutado este comando para crear mi proyecto, al que he llamado bii_log. Eso sí, lo tengo que crear debajo de mi nombre de usuario en Biicode, por si posteriormente lo quiero publicar.

bii new carlovin/bii_log --hello=cpp

Se ha creado una estructura de directorios y ficheros con nuestro proyecto. Aunque los únicos ficheros que vamos a tocar son:

blocks/carlosvin/bii_log/main.cpp
blocks/carlosvin/bii_log/biicode.conf

En el fichero biicode.conf vamos a configurar nuestras dependencias, en nuestro caso la librería Poco.

# Biicode configuration file

[requirements]
    fenix/poco(develop): 0

[parent]
        carlosvin/bii_log: 0

[includes]
    Poco/*.h: fenix/poco/Foundation/include

En la sección [includes], estamos redefiniendo la ruta a nuestros ficheros de cabeceras, porque si no lo hiciéramos tendríamos que hacer algo así:

#include "fenix/poco/Foundation/include/Logger.h"

Gracias a esta línea, en nuestro código tendremos includes más claros como:

#include "Poco/Logger.h"

Así de fácil, ya podemos utilizar la librería Poco en nuestro proyecto, por ejemplo:

#include "Poco/FileChannel.h"
#include "Poco/FormattingChannel.h"
#include "Poco/PatternFormatter.h"
#include "Poco/Logger.h"
#include "Poco/AutoPtr.h"

using Poco::FileChannel;
using Poco::FormattingChannel;
using Poco::PatternFormatter;
using Poco::Logger;
using Poco::AutoPtr;

int main(int argc, char** argv) {
        AutoPtr<FileChannel> pChannel(new FileChannel);
        pChannel->setProperty("path", "log/sample.log");
        pChannel->setProperty("rotation", "100 K");
        pChannel->setProperty("archive", "timestamp");

        //AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
        AutoPtr<PatternFormatter> pPF(new PatternFormatter);
        pPF->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");
        AutoPtr<FormattingChannel> pFC(new FormattingChannel(pPF, pChannel));
        Logger::root().setChannel(pFC);

        Logger & logger = Logger::get("TestChannel");
        for(int i=0; i<10000; i++){
                poco_information(logger, "This is a info");
                poco_warning(logger, "This is a warning");
        }
        return 0;
}

Para compilar el proyecto simplemente hay que ejecutar:

bii cpp:build

Y para publicarlo y que cualquiera pueda utilizarlo, como nosotros hemos utilizado Poco:

bii publish

Además de lo sencillo que ha resultado todo, me ha gustado mucho que después de ejecutar "bii cpp:build", mi Eclipse con CDT indexaba perfectamente el código, así como el autocompletado y la navegación entre clases y métodos.

También he leído un artículo sobre lo bien que se integra con CLion: When CLion met biicode.

Mantenimiento de Software

Hace unos días en el trabajo tuve que rellenar un documento en el que debía indicar el tipo de mantenimiento que debía hacer y sólo tenía dos opciones: correctivo y adaptativo. Esto me extrañó, porque el cambio que yo quería hacer no terminaba de encajar en ninguno de los dos tipos.

Haciendo memoria sobre lo que había dado en mi primera asignatura de Ingeniería del Software, conseguí recordar tres tipos, pero me parecía que me faltaba un tipo.

Hoy por casualidad he encontrado mis apuntes de Ingeniería del Software y estos son los tipos de mantenimiento ordenados de más a menos porcentaje de tiempo dedicado.

Perfectivo: Actividades para mejorar o añadir nuevas funcionalidades requeridas por el usuario.
Adaptativo: Actividades para adaptar el sistema a los cambios en su entorno tecnológico (Hardware o software).
Correctivo: Corregir defectos en el hardware o software detectados por el usuario durante la explotación del sistema.
Preventivo: Actividades para facilitar el mantenimiento del futuro sistema.

Construir un proyecto C++ con Gradle

Introducción

La construcción, gestión de dependencias y distribución de mis proyectos es algo que cada vez me preocupa más, me gustaría encontrar una herramienta que unificara este proceso y fuese independiente del lenguaje, lo más parecido con lo que he trabajado ha sido SCons, Autotools, Ant, Maven y últimamente Gradle.

Llevo un tiempo haciendo algunas cosas con Gradle, pero siempre centrado en proyectos Java utilizándolo como reemplazo a Maven, porque que es más sencillo de usar y más rápido. También lo he utilizado en projectos Android y he sufrido la pareja Android Studio + Gradle en sus primeros matrimonios (porque yo he querido), actualmente está todo mucho más documentado y funciona muy bien.

Antes de nada hay que decir que la construcción de proyectos C/C++ y Objective-C con Gradle se encuentra en fase de incubación, aunque ya permite hacer algunas tareas avanzadas como:

  • Generación de múltiples artefactos dentro del mismo proyecto, esto es varias librerías o ejecutables.
  • Gestionar las dependencias entre estos artefactos (sin versiones).
  • Generar distintos “sabores” de aplicaciones, por ejemplo: podremos generar una aplicación “Community” y otra con más características habilitadas llamada “Enterprise”.
  • Permite generar binarios para distintas plataformas, esto depende de las cadenas de herramientas ( Toolchains ) que tengamos instaladas en la máquina de compilación.

Como decía todavía tiene limitaciones, aunque están trabajando en ello y si consiguen lo que tienen planeado dejaré Autotools (me arrepentiré de haber dicho esto).

Leer más…