Esempi di app web di medie e grandi dimensioni create senza un ORM?

5

Ho letto un sacco di odio diffuso sugli ORM e sono sinceramente interessato a scoprire se esiste un modo migliore di fare le cose.

Sfortunatamente, mentre ci sono tonnellate di libri, post di blog e articoli sulla modellazione e sulla creazione di app che utilizzano vari ORM, nessuno esiste per l'approccio non-ORM.

Esistono webapp open source di medie e grandi dimensioni create senza un ORM? Niente batte leggendo il vero codice sorgente.

In alternativa, ecco cosa sto cercando di scoprire:

  1. solo perché non stai usando un ORM, varie associazioni tra tabelle non scompaiono. Come li modellerai nella tua app?
  2. Per cose semplici come il caricamento di una riga dato il PK, quanta piastra è necessario scrivere? Ci sono librerie che alleviano questo dolore?
  3. Per cose semplici come il caricamento 1: molte associazioni, quanto è necessaria la piastra di riscaldamento?
  4. Che aspetto ha il codice quando esponi un'API JSON nella tua app web? L'API JSON avrà intrinsecamente delle associazioni al suo interno (ad esempio, l'utente JSON avrà dei post al suo interno, che avranno dei commenti al suo interno). In che modo questo è materialmente diverso da ciò che fa un ORM?
  5. Che aspetto ha l'API di basso livello, che implementa varie logiche di business? Se non oggetti, quali argomenti prende questa API? Cosa restituisce?

La mia esposizione agli ORM è solo tramite Rails / ActiveRecord, e non riesco a visualizzare me stesso scrivendo un'applicazione con DB backed senza duplicare tutto lo standard che Rails si prende cura di me. Potrebbe essere che ibernare o nibernare sono animali diversi e il record attivo va bene. Il che avrebbe in realtà un senso e porre la questione a riposo. Almeno per me.

    
posta Saurabh Nanda 03.08.2016 - 05:44
fonte

4 risposte

7

Stack Overflow utilizza un micro-orm. A differenza di Hibernate, è solo un sottile rivestimento per le query SQL.

Risposte alle tue domande:

  1. Scrivendo una query SQL.

  2. Può variare da una riga di codice a circa 40. Avere un DTO già pronto aiuta.

  3. Il DTO ha lo stesso aspetto. Potresti eseguire 2 query invece di una, e caricare il secondo lazy.

  4. In Javascript, JSON si traduce direttamente in oggetti Javascript. Per altre lingue, puoi usare qualcosa come Newtonsoft.Json per tradurre il JSON in oggetti nella tua lingua preferita.

  5. Ecco alcuni esempi di codice:

    var result = database.ExecuteSqlQuery<Customer>(
        "SELECT * FROM Customer WHERE CustomerID = {0}", customerID).FirstOrDefault();
    

Dove ExecuteSqlQuery restituisce un elenco di oggetti Customer . Customer è una classe che contiene membri pubblici corrispondenti ad alcune o tutte le tue colonne nella tua query.

Dapper è in grado di eseguire query esattamente come questa. Restituirà una collezione di oggetti di un tipo specifico o una collezione di oggetti dynamic .

    
risposta data 03.08.2016 - 06:12
fonte
3

Dovresti controllare Elixir's Ecto, che è un linguaggio specifico del dominio (DSL) per interagire con database (per lo più relazionali) e scrivere query. Va bene con il framework Phoenix, che implementa il pattern MVC lato server.

Per rispondere alle tue domande:

  1. & 3. Le relazioni di modellazione sono fatte nel modello. L'elisir è un linguaggio funzionale, non ha classi. Invece si dichiarano schemi di tabelle usando Ecto DSL, che sostanzialmente estende la lingua di Elisir per fornire dichiarazioni di database brevi ed espressive. Per ogni tabella nel tuo database hai un modulo modello e un file di migrazione corrispondente (per eseguire una migrazione db una volta in ogni ambiente (dev, test, prod ecc.))

Ad esempio, un modello utente può essere simile al seguente


    defmodule MyApp.User do
    . . .
      schema "users" do
        field :name, :string
        field :email, :string
        field :bio, :string
        field :number_of_pets, :integer

        has_many :videos, MyApp.Video
        timestamps()
      end
    . . .
    end

Fa riferimento a un modulo Video che può apparire come:


    defmodule MyApp.Video do
    . . .
      schema "videos" do
        field :name, :string
        field :approved_at, Ecto.DateTime
        field :description, :string
        field :likes, :integer
        field :views, :integer
        belongs_to :user, MyApp.User

        timestamps()
      end
    . . .
    end
  1. Questo può essere fatto aggiungendo qualcosa di simile al modulo UserController:

    def show(conn, %{"id" => id}) do
      user = Repo.get!(User, id)   
      render(conn, "show.json", user: user)
    end

In quanto sopra, MyApp.User è stato sottoposto a alias come User . Il Repo o repository viene fornito con Ecto e insieme a un adattatore per database (come Postgrex per Postgres) che esegue il mapping sull'archivio dati sottostante.

  1. Considera i seguenti snippet:

    # Create a query
    query = from p in Post,
              join: c in Comment, where: c.post_id == p.id

    # Extend the query
    query = from [p, c] in query,
              select: {p.title, c.body}

In sostanza, si utilizza quanto sopra per interrogare il database, consegnando i risultati richiesti a una funzione render che renderebbe JSON.

  1. Elixir ha changesets che sono essenzialmente una serie (o una pipeline) di trasformazioni che i dati in arrivo (la struct) subiscono prima di essere pronti per essere commessi nel database. Durante la gestione della struttura in arrivo, i changeset consentono il filtraggio, la trasmissione, la convalida e la definizione dei vincoli. Ad esempio, quanto segue può essere una funzione di changeset (fa parte del modulo MyApp.User (il modello)):

    def changeset(struct, params \ %{}) do
        struct
        |> cast(params, [:name, :email, :bio, :number_of_pets])
        |> validate_required([:name, :email, :bio, :number_of_pets])
        |> validate_length(:bio, min: 2)
        |> validate_length(:bio, max: 140)
        |> validate_format(:email, ~r/@/)
    end

Se hai dei vincoli di controllo, fanno anche parte della pipeline precedente e si trovano anche in un file di migrazione.

    
risposta data 28.09.2016 - 20:03
fonte
2

Esistono vari modelli di progettazione che sono utili per le applicazioni che non utilizzano ORM. Suggerirei di leggere il libro di Martin Fowler, Patterns of Enterprise Application Architecture , che include alcuni esempi utili, ma qui ci sono alcuni brevi riassunti:

  • Gateway dati tabella - crea una classe che ha operazioni per manipolare una singola tabella
  • Gateway dati riga : crea una classe che rappresenta una singola riga in una tabella di database e fornisce operazioni che interagiscono con gli oggetti del modello di dominio
  • Oggetto query - una classe che rappresenta una query SQL, con proprietà per l'aggiunta di restrizioni alla query, selezione di colonne, ecc.
  • Transaction Script - un oggetto che incapsula tutto il lavoro svolto con il database durante una singola operazione

È anche possibile implementare il pattern Active Record con cui dovresti avere familiarità senza usare un ORM; devi solo implementare i metodi di ricerca e salvataggio di ogni classe a mano. Ma questo probabilmente sconfigge lo scopo di non usare un ORM, che generalmente ti consente di essere più flessibile nel modo in cui interagisci con il database.

    
risposta data 04.08.2016 - 09:04
fonte
0

Ho una piccola applicazione per i tornei di calcio: link

All'interno del dominio del programma, Game è una ar (radice aggregata) con associazioni a GameTeams e GameOfficials.

Il finder del gioco assomiglia a:

public function findGames(array $criteria)
{
    $conn = $this->gameConn;

    // Find unique game ids (might require several queries)
    $gameIds = $this->findGameIds($conn,$criteria);

    // Load the games
    $games = $this->findGamesForIds($conn,$gameIds);

    // Load the teams
    $wantTeams = isset($criteria['wantTeams']) ? $criteria['wantTeams'] : true;
    if ($wantTeams) {
        $games = $this->joinTeamsToGames($conn, $games);
    }

    // Load the officials
    $wantOfficials = isset($criteria['wantOfficials']) ? $criteria['wantOfficials'] : false;
    if ($wantOfficials) {
        $games = $this->joinOfficialsToGames($conn,$games);
    }

    // Convert to objects
    $gameObjects = [];
    foreach($games as $game) {
        $gameObjects[] = Game::createFromArray($game);
    }
    // Done
    return $gameObjects;
}

Quindi troviamo un sacco di giochi usando matrici di valori / chiave, eventualmente aggiungere team e funzionari per poi convertirli in un oggetto di gioco.

Una semplice query carica i giochi dal database:

private function findGamesForIds(Connection $conn, $gameIds)
{
    if (!count($gameIds)) {
        return [];
    }
    $sql = 'SELECT * FROM games WHERE gameId IN (?) ORDER BY gameNumber';
    $stmt = $conn->executeQuery($sql,[$gameIds],[Connection::PARAM_STR_ARRAY]);
    $games = [];
    while($game = $stmt->fetch()) {
        $game['teams']     = [];
        $game['officials'] = [];
        $game['gameNumber'] = (integer)$game['gameNumber'];
        $games[$game['gameId']] = $game;
    }
    return $games;
}

Abbastanza facile. Si noti che non provo a trasformare i nomi delle colonne delle tabelle in nomi di proprietà degli oggetti. Io uso solo ciò che è nella tabella.

Aggiungere le squadre è un po 'più di lavoro:

private function joinTeamsToGames(Connection $conn, array $games)
{
    if (!count($games)) {
        return [];
    }
    $sql = <<<EOD
SELECT 
  gameTeam.gameTeamId,gameTeam.gameId,gameTeam.gameNumber,
  gameTeam.slot,gameTeam.misconduct,
  poolTeam.regTeamId,poolTeam.regTeamName,
  poolTeam.division,poolTeam.poolTeamKey

FROM      gameTeams AS gameTeam
LEFT JOIN poolTeams AS poolTeam ON poolTeam.poolTeamId = gameTeam.poolTeamId
WHERE gameTeam.gameId IN (?)
ORDER BY gameNumber,slot
EOD;
    $stmt = $conn->executeQuery($sql,[array_keys($games)],[Connection::PARAM_STR_ARRAY]);
    while($gameTeam = $stmt->fetch()) {
        $gameId = $gameTeam['gameId'];
        $games[$gameId]['teams'][$gameTeam['slot']] = $gameTeam;
    }
    return $games;
}

Si noti che in questo contesto, un GameTeam viene effettivamente memorizzato in due tabelle diverse. Questo è il genere di cose con cui l'ORM che ho usato in precedenza ha avuto problemi. Potrei facilmente recuperare un GameTeam con un PoolTeam correlato ma non c'era un modo semplice per unire i due insieme.

Devo anche sottolineare che GameTeam.misconduct è un oggetto valore serializzato.

Ho diverse altre AR con cercatori simili. Nel complesso, ritengo che questo approccio sia più semplice da mantenere del mio precedente approccio basato su ORM. L'ORM ha fatto un po 'della query sulla piastra della caldaia per te, ma ho scoperto che era difficile da personalizzare.

Completa il codice sorgente (verruche e tutto) qui: link

    
risposta data 04.08.2016 - 16:24
fonte

Leggi altre domande sui tag