Minkä backend frameworkin ottaisin vuodelle 2021?

Olen työni puolesta tehnyt webkehitystä 12 vuotta ja harrastusmielessä pari vuotta enemmän.
Periaatteessa kaikki web-sovellukset ovat (nykyään) samanlaisia; spa-frontti, crud(create,read,update,delete)-rajapintoja sekä muutama integraatio.
Joskus samaa rajapintaa käytetään myös mm. mobiilisovelluksissa.

Peruslaiskana ihmisenä minulla on pieni fiksaatio tuottavuuteen. Olen aina valmis muuttamaan tapojani ja ohjelmoinnin kohdalla työkalujani, jos sillä tavoin pääsen pienemmällä vaivalla samaan lopputulokseen (tai saan samalla vaivalla enemmän valmista).
Päätin tutkia kunnolla mitä teknologioita on tarjolla ja onko oikeastaan väliä millä tekee. Löytyisikö framework, jolla saisi tehtyä saman kuin millä tahansa muullakin, mutta vähän helpommin?

Milloin ohjelmointi on vaivatonta ja tuottavaa?

Toki noiden lisäksi on mm. oppimiseen, ylläpidettävyyteen sekä makuasioihin liittyviä tekijöitä. Olen nyt kuitenkin suosion, toiminnallisuuksien ja elinvoimaisuuden näkökulmasta tonkinut bäkkäri-frameworkeja pari kuukautta, joten tässä löydöksiä tähän mennessä.

Ohjelmointikielten rajaus

Käytin seuraavia lähteitä teknologioiden suosion vertailuun

Yhdistämällä em. tilastot voidaan ohjelmointikielet laittaa suosituimmuusjärjestykseen

Kieli Tiobe PyPL Stackshare Stack overflow SH Framework
Python 3 1 2 4 1 (Django)
JavaScript 7 3 1 1 4 (Express)
Java 2 2 4 5 6 (Spring)
C# 5 4 6 7 2 (ASP.NET)
PHP 8 6 3 9 3 (Laravel)
C 1 5 17 11
C++ 4 5 15 10
R 9 7 17
Objective-C 18 8 14 20
Swift 16 9 13 16
TypeScript 46 10 5 8 4 (Express)
Matlab 15 11
Kotlin 33 12 18 13 6 (Spring)
Go 14 13 12 12
Ruby 13 15 7 14 5 (Rails)
VBA 14 15
Rust 25 16 8 19
Scala 21 17 16 21
Visual Basic 6 18
Dart 26 21 10 22

Vain harvalle ohjelmointikielelle löytyy suosittu web framework, joten top 5 bäkkärikieliksi on helppo saada Python, JavaScript/TypeScript, Java, C# ja PHP.

Frameworkien rajaus

Frameworkin ei tarvitse kuitenkaan olla se kaikkein suosituin niinpä seuraavaksi etsin valituille ohjelmointikielille muitakin bäkkäri-frameworkeja Stacksharesta sekä nettihauilla. Katsoin frameworkeille Stackshare-käyttäjien lukumäärän, viimeisimmän julkaisupäivän, githubin contributors-lukeman sekä Stack overflow:sta tägättyjen kysymysten määrän. Jätin jäljelle ne, jotka

Tarkempaan vertailuun jäi seuraavat 29 frameworkia (pudotetut yliviivattu)

Python:
AIOHTTP, Django, Django REST Framework, Falcon, FastAPI, Flask, Tornado
Apistar, Bottle, Cornice, Eve, Flask Api, Flask restful, Guillotina,
Hug, Sandman2, Sanic, Tastypie, Turbogears, Vibora, Web2py

Java:
Akka HTTP, Dropwizard, Micronaut, Play framework, Quarkus, Spring, Vert.x
CRNK, Elide, Javalin, Jersey, Jooby, Micro-server, Rapidoid, Ratpack,
Rest.li, RestEasy, RestExpress, Restlet, RestX, Spark Java, Spincast

JavaScript/TypeScript:
Express, Hapi, Feathers, Koa, NestJS, Sails
Actionhero.js, AdonisJS, DerbyJS, Fastify, Huncwot, KeystoneJS, LoopBack, Meteor, Moleculer, QEWD.js, Restify, ThinkJS, Tinyhttp, Total.js

C#:
ASP.NET Core, ServiceStack
ASP.NET, Carter, LightNode, Nancy, Restier

PHP:
CakePHP, CodeIgniter, Laravel, Phalcon, Slim, Symfony
API platform, Laminas, Lumen, Nette, PSX, Silex, Spiral, Waterpipe, Yii

Frameworkien ominaisuuksien vertailu

Frameworkien dokumentaatioita selaamalla kokosin 50 yleistä toiminnallisuutta. Jätin pois sellaisia mitä en koe nykyään juuri tarvittavan spa-clientin kanssa (esim. cookiet, sessiot ja template enginet). Arvioin työläysasteen näille toiminnallisuuksille kaikilla frameworkeilla virallisesta dokumentaatiosta ja nettihauilla.

Käytin seuraavaa pisteytystä
1 = copy-paste ratkaisu löytyy suoraan dokumentaatiosta
2 = copy-paste ratkaisu löytyy netistä / valmis kirjasto
3 = ei löydy suoraa vastausta, mutta hakutuloksia / kirjastoja yhdistelemällä onnistunee (= jotain sinne päin)
4 = ei relevantteja hakutuloksia (katsoin ensimmäiset ~10)

Toiminnallisuudet

RESTful routing (method + path + parameters)

// Express
app.get('/products/:id(\\d+)', function (req, res) {
  // ...
})

Reverse routing (urlin generointi reitin nimellä + parametreilla)

# Django
urlpatterns = [
    path('products/<int:id>/', views.product_details, name='product-details'),
]

id = 123
url = reverse('product-details', args=(id))

Semantic versioning (headerissä arvo mitä versiota client haluaa käyttää)

// Restify
server.get('/products/:id', restify.plugins.conditionalHandler([
  { version: '1.1.0', handler: getProductByIdV1 },
  { version: '2.0.0', handler: getProductByIdV2 }
]));

Subdomain routing

// Laravel
Route::domain('{client}.example.com')->group(function () {
    Route::get('products', function ($client) {
        // ...
    });
});

Static files (tiedostot hakemistossa /x tarjotaan polusta /y)

// ASP.NET Core
app.UseStaticFiles(new StaticFileOptions
{
   FileProvider = new PhysicalFileProvider("path/to/files"),
   RequestPath = "/assets"
});

Rate limiting (esim. x kutsua minuutissa)

// Laravel
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000);
});
Route::middleware(['throttle:global'])->group(function () {
    Route::get('/products', function () {
        //
    });
});

Middleware: Decorate request, terminate, filter, modify response (muokkaa requestia/responsea)

# AIOHTTP
@middleware
async def example_middleware(request, handler):
  # before / filter / decorate / terminate
  resp = await handler(request)
  # after
  return resp

Middleware conventions (määrittele joustavasti missä middlewarea käytetään)

// ASP.NET Core
[ExampleFilter] // apply to all sub classes
public abstract class ExampleControllerBase : ControllerBase {}

[ExampleFilter] // apply to all actions
public class ExampleController : ControllerBase 
{ 
  [ExampleFilter] // apply to single action
  [HttpGet("")]
  public IActionResult SomeAction() {}
}

Content-negotiation (konvertoi json/xml headereitten mukaan)

// ASP.NET Core
// Add XML support
services.AddControllers()
  .AddXmlSerializerFormatters();

Dependency injection

// NestJS
export class ProductsController {
  // service is injected
  constructor(private productService: ProductService) {}
}

Model binding

// NestJS
@Put(':id')
updateProduct(@Param('id') id: string, @Body() product: ProductModel) {
  // ...
}

Validation (api heittää virheen, jos ei ole validi)

// NestJS
export class ProductModel {
  @IsNotEmpty()
  name: string;
  // ...
}

Caching (esim apin tulos cachetetaan joksikin aikaa)

// ASP.NET Core
public class NewsController : Controller
{
  [HttpGet]
  [ResponseCache(VaryByQueryKeys = new[] { "category" },  Duration = 300)]
  public NewsModel GetMostReadNews(string category)
  {
    // ...
  }
}

Auth:
Username+password, Google/Facebook/..., roles, jwt, policies, conventions

OpenAPI (apien dokumentointi)

Messaging:
Broadcasting (websocket), events

Task scheduling

// NestJS
@Injectable()
export class TasksService {

  @Cron('0 */5 * * * *')
  handleCron() {
    // ...
  }
}

GraphQL:
queries, resolvers, mutations, subscriptions

Tulokset

Lähes kaikissa frameworkeissa oli joitakin toiminnallisuuksia, joihin ei löytynyt ratkaisua tai ne olivat työläitä (kts. kuvan tummat pystypalkit).


Yleisimmät ongelmat

Top frameworkit

10. Symfony 98p (1.96p/toiminnallisuus)
Juurikin em. suurimmat puutteet. Lisäksi GraphQL erityisen työläs.

9. Quarkus 96p (1.92p/toiminnallisuus)
Semanttinen versiointi puuttuu. Autentikaatio työläs some-tileillä.

9. Django 96p (1.92p/toiminnallisuus)
Model bindingin puutteesta aiheutuu toistuvaa työtä. View-ajatusmalli sopii huonosti RESTful-routeihin.

7. Django REST framework 95p (1.9p/toiminnallisuus)
Samat kuin perus Djangossa.

6. ServiceStack 84p (1.68p/toiminnallisuus)
Semanttinen versiointi puuttuu ja GraphQL-tuesta ei löydy mitään. GraphQLn takia ei pääse top5:een.

5. Laravel

Dokumentaatio oli esimerkillinen. Hyviä copy-paste -esimerkkejä moneen toiminnallisuuteen. Suurin ongelma Laravelissa on vahvasti tyypitettävien requestin ja responsen puute. Tästä aiheutuu toistuvaa validointilogiikkaa. Lisäksi OpenAPI-kuvauksiin pitää toistaa uudestaan sama schema, joka on jo validointia varten määritelty.

80p (1.6p/toiminnallisuus)

Hyvää

Huonoa

4. Spring Boot

Pisteitään paremman sijoituksen Spring ansaitsee runsailla toiminnallisuuksillaan. Mitään ei jää tekemättä. Ratkaisut tosin löytyivät useammin baeldung.com sivustolta kuin virallisesta dokumentaatiosta mikä saattaa olla ongelma, jos ko. sivustoa ei päivitetä.

91p (1.82p/toiminnallisuus)

Hyvää

Huonoa

3. FastAPI

Mukavan minimaalinen lähestymistapa. Suurin osa toiminnallisuuksista helppo toteuttaa. Valitettavasti osaan (harvemmin käytettyjä?) toiminnallisuuksia ei löytynyt ratkaisua.

87p (1.74p/toiminnallisuus)

Hyvää

Huonoa

1. NestJS / ASP.NET Core

NestJS:n dokumentaatio oli hyvin selkeä ja toiminnallisuuksia löytyi mukavasti. ASP.NET Core puolestaan oli ainoa framework, jolle kaikkiin toiminnallisuuksiin löytyi helppo ratkaisu.

Näiden paremmuus riippuu aika paljon kielen (TypeScript/C#) ja alustan (Node/.NET Core) hyödynnettävyydestä.

NestJS

72p (1.44p/toiminnallisuus)

Hyvää

Huonoa

Vaihtoehto #1 jos

ASP.NET Core

60p (1.2p/toiminnallisuus)

Hyvää

Huonoa

Vaihtoehto #1 jos