Entitats amb fitxers: tot junt o per parts?

Hi ha dues propostes per a pujar (actualitzar-les també) unes entitats que porten vinculades a la imatge:

  • [POST] /api/v1/imatge amb un cos html serialitzat tipus { desciption: “”, tags: [“”, “”,””] }
  • [POST] /api/v1/image/{id}/upload amb un multipart/form-www image=@adreçaImatge

Amb:

  • [POST] /api/v1/imatge amb un multipart/form-data description=”” ; tags=[“”,””,””]; image=@adreçaImatge

I aquí és on premia qui ha de fer ús de la API, quina utilització se li dona. El fet de treballar amb entitats sempre és com allò de que per a un martell tot són claus. Hauríem de separar:

  • Amb quina recurrència es pujaran imatges o serà un fet únic?
  • Les aplicacions clients volen crear l’entitat amb la imatge?
  • Les aplicacions clients com processen els multipart/form-data?

De les tres preguntes, les dues primeres són de disseny i la tercera d’arquitectura. Per allò del que els backends poden viure a esquenes de la realitat si ofereixen unes portes definides. Són importants perquè poden encorsetar bastant el processament de les dades (no és el mateix un form què un body serialitzat).

Amb NodeJS tenim multer què a mode intermediari (middleware) ens pot emmagatzemar la imatge en un directori, i posar-nos les dades del form-data al body com a propietats. En el següent exemple es fa servir la segona opció:

routes.js

  app.post("/v1/imatge", imatgeController.uploadImg,  imatgeController.save);

imatgeController.js

const multer = require('multer');

const fs = require('fs');

const imageStorage = multer.diskStorage({
  destination: function (req, file, cb) {

    if (!fs.existsSync('./imageUploads')) {
      fs.mkdirSync('./imageUploads');
    }

    cb(null, './imageUploads');
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  }
});

exports.uploadImg = multer({ storage: imageStorage }).single('image');
exports.save = async (req, res) => {

var entity = { description: req.body.description, image: req.file.filename, tags: req.body.tags };
...
}

El fet de que podem sobrecarregar amb diverses funcions les rutes també ens facilitarà altres tipus de processaments (autenticació, per exemple), però això ja és cosa més de NodeJS i ExpressJS.

La solució ens sobrecarrega bastant el què és la entitat: la carrega de imatge per un costat, què pot tenir més lògica (compressió, tractament, desplaçament a un lloc remot) i pot fer més pesat del que és en si el pujar unes dades per a una entitat.

Per a fer un punt comú entre client i servidor, el codi swagger.json descriurà els camps com a formData parant especial atenció en el type:

"/v1/imatges": {
      "post": {
        "tags": [
          "imatges"
        ],
        "summary": "Afegir una imatge",
        "description": "Ruta per afegir una nova imatge",
        "consumes": [
          "image"
        ],
        "parameters": [
          {
            "name": "imatge",
            "in": "formData",
            "description": "afegir nova imatge",
            "required": "true",
            "type": "file"
          },
          {
            "name": "descripcio",
            "in": "formData",
            "description": "descripció de la imatge",
            "required": "true",
            "type": "string"
          }

        ], 
...

Per al client, fem l’exemple de un swagger tipificat i l’utilitzem en un client, en aquest cas, NSwag amb destí C#. El resultat és una funció utilitzable per aquest client:

        /// <summary>Create a banner</summary>
        /// <param name="image">Adding new publication.</param>
        /// <param name="name">Banner name</param>
        /// <param name="isDisabled">Is disabled?</param>
        /// <param name="analyticCode">Analytic Code</param>
        /// <exception cref="SwaggerException">A server side error occurred.</exception>
        public System.Threading.Tasks.Task Banners4Async(FileParameter image, string name, bool isDisabled, string analyticCode)
        {
            return Banners4Async(image, name, isDisabled, analyticCode, System.Threading.CancellationToken.None);
        }

Quina seria la diferència de fer-lo per separat? En un altre post…

Trampes al solitari

Dinahosting per defecte té configurats els servidors per a obligar a rebre peticions des clients web.

Una solució és muntar les capceleres amb aquesta informació:

req.Accept = “text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8″;
req.UserAgent = ” Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0″;

 

Google Optimization Tools en una Azure Function

La suite de Optimización de Google, Google Optimization Tools (ortools), ofrece un amplio abanico de herramientas de constraint programming que normalmente son de pago (como Frontline, Groubi ).

Debido a la necesidad de desarrollo de prototipos este solver ofrece una posibilidad de prototipar un desarrollo de manera funcional para saber si los modelos matemáticos son sadisfactibles antes de facturar al cliente un proyecto complejo.

Para desgracia de nuestro entorno de trabajo y como ya es constumbre en Google, los wrappers de C# estan en funciones obsoletas. Para nuestra fortuna, la libreria de Python está al día aunque a día de hoy (09-2018) Python en Azure Functions a dia de hoy está en experimental.

1.- Instalar Python x64 a la Azure Function

La versión que por defecto funciona en Azure Function es la 2.6, la cual el package de ortools NO soporta. También por defecto se ejecuta en x86 así que procederemos a activar el x64 y posteriormente instalar la versión más reciente que nos permiten desde Azure, la 3.6.4.

Primero activamos el modo x64 al servidor de funciones:

Platform features > Application settings y click a 64-bit a Platform

A partir de ahora tendremos que realizar acciones de consola. Algunas se pueden hacer visualmente, pero a través de consola se explican aquí o aquí. De manera visual hay que acceder al kudu a través de esta dirección:

https://[NombreDelServerDeFunciones].scm.azurewebsites.net

Accedemos entonces a “Site extensions” y añadimos Python 3.6.4 x64

Posteriormente volvemos a Platform features > Application settings para añadir estas lineas:

Handler mappings

EXTENSION
SCRIPT PROCESSOR
ARGUMENTS

Application settings

APP SETTING NAME
VALUE
0

Una vez finalizados estos pasos, podemos proceder a reiniciar el servidor de App Functions y proceder a instalar la libreria ortools con pip.

Volvemos a Kudu y tecleamos la instalación de ortools a través a pip:

python -m pip install ortools

 

Latència de una Azure Function

Revisió gener 2020: Aquesta entrada va ser escrita al 2018 en plena fase de desplegament. Actualment les Azure functions v2 i v3 ofereixen el always on què disminueix dràsticament la latència de les funcions. Les funcions escrites amb Net Framework només poden anar amb V1 que ara mateix està en camí a deprecated.

Des Azure fa un temps els webjobs de les WebApps d’Azure s’estan traslladant a funcions. Això el teu els seus avantatges, com no consumir recursos de les instàncies de WebApp i consumir-ne sota demanda.

Tot és correcte fins que les versions actuals de serveis no ofereixen quelcom bàsic: estar sempre actives. Això és vital qual la funció es dedica a llegir una cua: o pot estar desactiva sempre o pot estar osiosa esperant a respondre. Les Azure Functions al no tenir Always On són el primer cas: desactiva fins que es digui el contrari. Doncs es plantegen dos solucions: (A) fer un pas enrere i tornar a workers o (B) fer un mecanisme de mantenir sempre viu.

Imatge
La latència de les respostes del encadenat de processos és alt degut al temps de warm up de una Azure Function

La solució A implica desplegar una sèrie de llibreries que tenen ús crític de la CPU a una instància WebApp que ja de per si ja té pics de CPU. Descongestionar la instància de aquests pics és una prioritat.

La solució B implica refórmular el codi de lectura de cues per a que quan a la senyal de vida, una senyal que anomenarem keep_alive. Un altre funció Azure inserirà a la cua cada X (definirem el temps com una variable de configuració per a trobar el rendiment òptim) un missatge de keep_alive que els diferents processos encadenats en cues només hauran de retransmetre de la cua d’entrada a la de sortida.

És una forma de mantenir els processos de la cua ociosos, tampoc és una novetat. Són principis que vaig veure a clustering amb heartbeat/pacemaker o més simples, per evitar que el IIS decidís que era bon moment posar-se en repòs.

El problema és què pot saturar les cues (1), pot sobrecarregar els serveis (2) i pot implicar un consum extra (3). Així que caldrà trobar el equilibri entre mantenir una latència optima: una repetició de la senyal keep_alive que no desajusti el sistema i dispari els costos.

Imatge
En vermell: un keep alive cada 10 segons, en blau cada 20 segons, en verd cada 30.

Provem 3 períodes per a l’enviament de keep_alive: cada 30 segons, cada 20 segons i després cada 10. La Azure Function té aproximadament un període de 10 segons per a aixecar-se (warm up a actiu) i de repòs molt menor (al acabar ja passa a repòs).

El fet de mantenir-lo a 30 s’aconsegueix que les peticions intercalades entre les senyals de vida passin de pics de 120 a 60. Segueix sent un temps d’espera elevat per al client, que voldria resoldre les peticions en el menor temps possible.

Reduint el keep_alive a 20 segons reduïm els pics a 30 segons. La meitat de la meitat. I a 10 segons aconseguim que els pics es redueixin a menys de 10 segons.

El curiós de tot això és que són processos que a la versió de desktop mai vam aconseguir que arribessin a aquests números. Les Azure Functions amb una bona conjunció de SQL Server i optimització poden arribar a mostrar molt bons resultats.

Versionatge de WCF (II)

L’arquitectura de serveis ha de mostrar certa flexibilitat a l’hora d’incloure noves funcions i contractes de dades.

Afegir noves propietats a una classe del contracte implicarà el trencament del mateix. Això ho solucionarem amb el versionatge i fent estàtiques les funcions i propietats de les classes que s’implementen a cada versió, augmentant quan es requereixi canviar-les.

Doncs és oportú desar la lògica en un nucli consultable per les diferents versions: la BE (Business Entities) on s’especificaran entitats que seran fàcilment convertibles via cast des el DAL a les diferents versions. La BLL implementarà al lògica pròpia de consultes, accessos i demés a les dades, implementada via interfície de getters per poder accedir a diferents tipus de lògiques. La DAL serà la classe que més variarà en cada canvi de dades i que estarà redefinida via Entity Framework.

 

Versionatge de WCF (I)

El canvi de propietats d’un contracte, des afegir una propietat o treure-la a quelcom més comú com afegir més elements a un enum pot implicar el trencament de contractes.

Per aquest motiu és fa important tenir un versionatge dels [DataContracts] així com també de les funcions en si.

Treballant amb WCF hi ha un element que ens facilitarà força la posibilitat de múltiples versions d’un contracte treballant en un mateix servei: els namespace. Per aquesta raó, aïllar les versions en namespaces diferents ens afegirà possibilitat de treballar en diferents versions sense necessitat de canvis traumàtics que impliquin renovar referències en múltiples projectes de cop.

<%@ ServiceHost Language="C#" Debug="true" Service="Agrifood.CloudFI.Service.v1.FormulaIntegrationCloudService" CodeBehind="ServiceCloudFI.svc.cs" %>

Referències:

https://msdn.microsoft.com/en-us/library/ff384251.aspx
https://msdn.microsoft.com/en-us/library/ms731060(v=vs.110).aspx
http://devproconnections.com/development/versioning-wcf-services-part-i