Mvc vs Routing Middleware

Quando si realizza un servizio web bisogna sempre definire quello che sarà il perimetro di competenza del servizio per massimizzarne sicurezza e performance. A tale scopo potrebbe essere utile, nella realizzazione di un servizio RESTful, evitare l’utilizzo di MVC ma eseguire il route utilizzando il Routing Middleware sul quale MVC si basa.

Mvc e Routing Middleware: le differenze

Vediamo, prima di tutto, cosa cambia a livello di implementazione:

With mvc.

Il progetto è disponibile al seguente link Labs.MvcLover

Labs.MVCLover

 

WooController.cs

namespace Labs.MvcLover.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class WooController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public JsonResult Get()
        {
            return new JsonResult(new Woo());
        }
    }
}

 

Startup.cs

namespace Labs.MvcLover
{
    public class Startup

{
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }
}
 

Without mvc.

Il progetto è disponibile al seguente link Labs.MvcHater

LabsMVCHater

WooEndpoint.cs

namespace Labs.MvcHater.Endpoints
{
    public class WooEndpoint
    {
        public String Get()
        {
            return JsonConvert.SerializeObject(new Woo());
        }
    }
}

 


Startup.cs

namespace Labs.MvcHater
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRouting();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouter(router =>
            {
                router.MapGet("api/woo", async (request, response, routeData) => await response.WriteAsync(new WooEndpoint().Get()));
            });
        }
    }
}

 

Notiamo che la differenza sostanziale risiede nel modo con il quale andiamo a definire la route: mentre nel primo caso andremo ad utilizzare il routing implicitamente offerto da MVC mediante app.UseMvc() all’interno del metodo Configure della classe Startup, nel secondo caso verrà definita una route puntuale mediante

app.UseRouter(router =>
{
    router.MapGet("api/woo", async (request, response, routeData)
        => await response.WriteAsync(new WooEndpoint().Get()));
});

 

Ok, ma perché mi dici questo?

Bombardier

Analizziamo, ora, cosa cambia a livello di performance.

Per eseguire i benchmark è stato utilizzato bombardier. Premessa: questi test sono stati eseguiti in ambiente locale, quindi i valori di latenza misurati sono inferiori a quelli che troviamo nel World Wide Web, ma danno, comunque, un’idea degli incrementi di performance raggiungibili con questo tipo di approccio.

Sfruttando il Routing Middleware otteniamo questo risultato #### Test A.

> bombardier -c 125 -n 10000 http://localhost:5000/api/woo
Bombarding http://localhost:5000/api/woo with 10000 request(s) using 125 connection(s)
 10000 / 10000 [==========================================================================================] 100.00% 27s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec       449.13    1236.52   25468.16
  Latency      343.58ms    61.83ms      1.01s
  HTTP codes:
    1xx - 0, 2xx - 10000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:   112.67KB/s

 

Test B

> bombardier -c 200 -d 10s -l http://localhost:5000/api/woo
Bombarding http://localhost:5000/api/woo for 10s using 200 connection(s)
[=================================================================================================================] 10s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec       253.17     964.44   18167.27
  Latency      761.32ms   231.95ms      2.62s
  Latency Distribution
     50%   834.00ms
     75%      1.02s
     90%      1.19s
     95%      1.45s
     99%      1.64s
  HTTP codes:
    1xx - 0, 2xx - 2722, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    48.31KB/s

Mentre, utilizzando MVC, il risultato ottenuto è il seguente: #### Test A.

> bombardier -c 125 -n 10000 http://localhost:5000/api/woo
Bombarding http://localhost:5000/api/woo with 10000 request(s) using 125 connection(s)
 10000 / 10000 [========================================================================================] 100.00% 1m25s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec       125.88     377.33    9518.08
  Latency         1.07s   302.64ms      5.72s
  HTTP codes:
    1xx - 0, 2xx - 10000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    41.68KB/s

 Test B

> bombardier -c 200 -d 10s -l http://localhost:5000/api/woo
Bombarding http://localhost:5000/api/woo for 10s using 200 connection(s)
[=================================================================================================================] 10s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec         6.75     134.89    2999.67
  Latency         7.70s      2.53s     10.21s
  Latency Distribution
     50%     10.12s
     75%     10.17s
     90%     10.19s
     95%     10.20s
     99%     10.20s
  HTTP codes:
    1xx - 0, 2xx - 84, 3xx - 0, 4xx - 0, 5xx - 0
    others - 200
  Errors:
    the server closed connection before returning the first response byte. Make sure the server returns 'Connection: close' response header before closing the connection - 200
  Throughput:     7.24KB/s

 

Cosa notiamo se analizzassimo questi dati con la lente di ingrandimento?

Richieste

Numero di richieste evase al secondo

More is better 

Requests_for_second

Notiamo come nel test A le differenze di performace a favore di MvcHather siano superiori rispettivamente del: - avg: 356.79% - stdev: 327.70% - max: 267.58%.

Mentre nel test B le percentuali salgono a: - avg: 3750.67% - stdev: 714.98% - max: 605.64%.

Le maggiori prestazioni ottenute nel secondo test sono dovute al fatto che il servizio è riuscito a gestire meglio la concorrenza evadendo richieste più velocemente.

Latenza di risposta

Less is better

 Latency

Questo test è la stretta conseguenza del precedente, dove la latenza è il delta risultante dalla data di invio della richiesta e la data di arrivo della risposta, in questo caso MvcHater performa meglio nel test A, ecco le percentuali: - avg: 311.43% - stdev: 489.47% - max: 566.34%.

Mentre nel test B le percentuali salgono a: - avg: 1011.40% - stdev: 1090.75% - max: 389.69%.

Volume di byte in uscita per secondo

More is better

Throughtput

Questo test serve per misurare il volume di byte in uscita dal servizio web, anche in questo caso MvcHater esce vincitore del 268.39% nel test A e del 667.26% nel test B.

Latenza distribuita

Less is better

Latency_distribution 

Questo test serve a misurare il percentile di distribuzione della latenza per il quale otteniamo i seguenti valori:

%

MvcLover

MvcHater

(MvcHater/MvcLover)%

50%

10.12

0.83

1219.27%

75%

10.17

1.02

997.06%

90%

10.17

1.19

854.62%

95%

10.20

1.45

703.45%

99%

10.20

1.64

621.95%

Codici di risposta http

Http_response_codes

Qui possiamo notare come il servizio MvcHater sia riuscito ad evadere tutte le 2722 richieste ricevute in 10 secondi da 200 utenti concorrenti.
Mentre il servizio MvcLover ha evaso con successo solamente 84 richieste delle 284 ricevute, pari al 30%.
Il totale delle richieste evase con successo da MvcHater rispetto a MvcLover è superiore del 958%.

Conclusioni

Il framework MVC è sicuramente un valido strumento per realizzare siti web, ma sempre con più frequenza (n.d.r.: fortunatamente!) si sente parlare di applicazioni web moderne, in cui vi è netta distinzione fra front-end e back-end. In questo caso, ed esempio, basare l’implementazione del proprio back-end su tale framework potrebbe aggiungere overhead e quindi calo di performance.

In questi casi eseguire un routing attraverso il motore di generazione delle url di Microsoft.AspNetCore.Routing è sicuramente una scelta vincente e segue la buona pratica del less code is better.

comments powered by Disqus