Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

Pravljenje novog SoundCloud

Prednji tim SoundCloud gradio je na osnovu iskustva i sarađivao sa HTML5 widget u pravljenju najnovijeg izdanja Next SoundCloud beta, koliko je to moguće. Deo učenja takođe uključuje deljenje iskustava, tako da vam ovde predstavljamo arhitekturu novog sajta.

Pravljenje jednostranih aplikacija

Jedna od glavnih karakteristika Next SoundCloud jeste neprekidni playback, koji dozvoljava korisnicima da započnu slušanje zvuka i nastavljanje istraživanja bez prekida iskustva. Pošto vas to ohrabruje u navigaciji po sajtu, onda želimo da vam to iskustvo bude stvarno prijatno. Ove karakteristike su bile dovoljne da nas podstaknu da napravimo Next kao jednostranu Java aplikaciju. Podatke smo izvukli iz našeg javnog API , ali svo istraživanje i navigacija dešavaju se u pretraživaču kada je u pitanju trenutna navigacija, bez potrebe da se napravi zahtev posebni na serveru.

Kao baza za ovu vrstu aplikacije, koristili smo veoma popularni Backbone.js . Ono što nas je privuklo Backbone (pored činjenice da mi već koristimo naš Mobile sajt i Widget), jeste da ne treba mnogo objašnjenja kako sve to funkcioniše. Backbone i dalje ima solidnu bazu za rad sa pogledima, data modelima i kolekcijama, ali su i mnoga pitanja neogovorena, a tu je i njegova jačina i fleksibilnost.

Za sve informacije sa prednjeg kraja koristili smo Handlebars templat sistem. Procenili smo još neke druge template i mašine, ali smo se zaustavili na Handlebars iz nekoliko razloga:

    Nije potrebna nikakva logika unutar templata, što vam je dobro kod podele briga.

    Može se ponovo kompajlirati pre odvajanja što dovodi do bržeg deljenja i manjeg polja za igru koje se mora poslati klijentu (vreme pokretanja biblioteke je 3.3kb čaki pre gzip).

    Dozvoljava da se definiše priručnik za potošače.

Modularni kod

Tehnika koju smo koristili kod Widget koja se pokazala kao veliki uspeh jeste pisanje kodova u modulima i objavljivanje svake nezavisnosti pojedinačno.

Kada se piše kod, mi ga pišemo u CommonJS-style modul koji se prebacuje u AMD modul prilikom pretraživanja u pretraživaču. Postoji razlog što smo se odlučili baš za ovaj korak, koji je možda najbolje pbjašnjan kada se vidi da svaki stil izgleda ovako:

// CommonJS module ////////////////
var View = require('lib/view'),
    Sound = require('models/sound'),
    MyView;

MyView = module.exports = View.extend({
    // ...
});

// Equivalent AMD module //////////
define(['require', 'exports', 'module', 'lib/view', 'models/sound'],
  function () {
    var View = require('lib/view'),
        Sound = require('models/sound'),
        MyView;

    MyView = module.exports = View.extend({
        // ...
    });
  }
);

    Ekstra define boilerplate je teško napisati

    Duplikat modulani nezavisnosti je takođe teško napisati i sklon je greškama

    Prebacivanje iz CommonJS to AMD je lako automatizovati, pa zašto ne, onda?

Za vreme lokalnog razvoja, prebacujemo se u AMD module za čas i koristimo RequireJS da bi smo ih posebno prebacili. To nač razvoj čini sasvim fikcionim pa ga možemo sačuvati, pa onda ponovo ubaciti da sve vidimo, ipak to nije toliko dobro za stvaranje jer metoda pravi stotine HTTP zahteva. Umesto toga, modul se koncentriše na nekoliko paketa, pa daje RequireJS za veoma lagane module prebacivanja kod AlmondJS .

CSS i Templati po zahtevima

Pošto već uključujemo sav naš kod definisanjem stvarnih zavisnosti, smatramo da ima smisla da se uključe CSS i tempalti za isti pogled. Kod templata sve radnje su šta više jednostrane kod Handlebars kompajlera tempalata kod Javascript funkcija. Za CSS, to je nova paradigma:

Pogled definiše koji CSS fajl je potreban za prioritet displeja, isto kao i što bi ste definisali java skriptu i modul koji morate da izvršavate. Samo kada definišete možete ubaciti CSS na stranicu. Naravno, imaju neke globalni uobičajeni sistemi, ali uglavnom, svaki pogled ima svoj mali CSS fajl koji se samo definiše stilom posmatrača.

Kada pišete kod, pišemo ga samo u vanila CSS (bez pomoći procesora kao što je SCSS ili LESS), ali pošto su uključeni u Require/Almondmoraju se prebaciti u module. To se radi pomoću koraka koji uokvirava stil u funkcije koje se vraćaju kao <style> DOM elemenat. Evo primera kako to u stvarnosti izgleda:

Input je čisti CSS

.myView {
  padding: 5px;
  color: #f0f;
}
.myView__foo {
  border: 1px solid #0f0;
}

Rezultat je AMD modul

define("views/myView.css", [...], function (...) {
  var style = module.exports = document.createElement('style');
  style.appendChild(
    document.createTextNode('.myView { padding: 5px; color ... }');
  );
})

Views kao komponente

Glavni koncept kod razvijanja Next jeste onaj kada se posmatraju pogledi i upotrebljive komponente. Svaki pogled može da uključuje druge ‘pod’ poglede, koji sami mogu da uključuju druge poglede i slično. Efekat ovoga je da su neki pogledi samo kompoziti drugih pogleda i da zauzimaju celu stranicu, dok drugi mogu biti samo mali deo svega, ili čak neka etiketa u svemu tome.

Ako ovi pogledi ostanu nezavisni to je veoma važno. Svaki pogled je odgovoran za svoje sopstveno ppodečavanje, dogašaje, podatke i pročišćavanje. Pogledi su ‘zabranjeni’ iz modifikovanja jer svaki od tih pogleda može biti uključen u bilo koji kontekst i možemo se osloniti na to da će raditi kao što se smatra da je.

Kao primer, ‘play’ dugme na Next jeste pogled. Da bi se uključio bilo gde na sajtu, sve što je potrebno da uradimo jeste daa napravimo odstojanje, pa onda da napravimo id svakog zvuka koji bi trebalo da pušta. Sve ostalo se rukuje ručno pomoću sopstvenog dugmeta.

Da bi se napraviili ovi prolazi, većina vremena se provodi unutar templata sličnih pogleda. To se postiže upotrebom našeg posebnog Handlebars upustva. Evo i odsečka iz templata koji mi koristimo i koji koristi dotični, gore pomenuti, view pomagač:

<div class="listenNetwork__creator">
  {{view "views/user/user-badge" resource_id=user.id}}
</div>

 

Kao što možete videti, dodavanje podpogleda jeste jednostavno kao i određeni modul imenovanog pogleda i dodavanje nekih minimalnih istanci i varijabli. Kada stvarno dođe do toga iza scene onda se to odvija na ovaj način:

Kada se podeli i raspodeli pogled, templat se mora vratiti svom prvobitnom nizu. A kada je uključeno i upoustvo za pogled, onda se atributi pomenuti prebacuju na templat, pa i referenca date klase, kod posebnog objekta koji ima id, i deo elemenata (mi koristimo < view data-id="xxxx">). Taj id je jedinstven, i jedan savim poseban broj. Nakon što se i tempal odeli, autput jeste niz koji se automatski stvara sam od sebe. U suštini, kod uradi ovo:

<div class="foo">
  <view data-id="123"></view>
</div>

 

Zatim smo našli čuvare mesta i zameniti te elementa sa subviews elementom koji automatski stvara sam za sebe. U suštini kod ovo radi

parentView.$('view').each(function () {
  var id = this.getAttribute('data-id'),
      attrs = theTemporaryObject[id],
      SubView = attrs.ViewClass,
      subView = new SubView(attrs);
  subView.render(); // repeat the process again
  $(this).replaceWith(subView.el);
});

Deljenje Modela između Views

Dakle sada imamo sistem gde ima nekoliko pogleda na ekranu, od kojih su mnogi pogledi istog modela. Uzmite, na primer, “listen” stranicu:

Postoji pogled za dugme play, naslov zvuka, waveforma, vreme ažuriranja zvuka (to se dinamično ažurira, pa je zato to pogled), i tako dalje. Za svaki od ovih pogleda postoji isti zvuk i model, ali i ne želimo da se svaki duplira. Moramo pronači model pomoču koga će se najlakše deliti neki model.

Takođe zapamtite da svaki od tih pogleda mora da se nosi sa slučajem kada nema još podataka. Skoro svi pogledi su inicirani samo pomoću id kao modela, tako da je sasvim moguće da se nije još učitao podatak za taj odrešeni model.

Da bi se to rešilo, koristimo konstrukcije koje nazivamo instance store. Ovo store jeste objekat kome se pristupa i koji se modifikuje svaki put kada se poziva konstruktor tog modela. Kada se model stvara prvi put, sam se ubrizgava u store, koristeći id kao jedinstveni ključ. Ako se poziva isti model konstruktora sa istim id, onda se sve vraća na početnu istancu.

var s1 = new Sound({id: 123}),
    s2 = new Sound({id: 123});

s1 === s2; // true, these are the exact same object.

 

Ovo funkcioniše zbog iznenašujuće malo poznatih skripti Javascript.Ako konstruktor vrati objekat, onda je varirana vrednost. Tako da ako vratimo referencu na istancu prvobitnog stvaraoca, onda dobijamo željeno ponašanje. Iza scene, konstruktor u stvari radi ovo:

var store = {};

function Sound(attributes) {
    var id = attributes.id;

    // check if this model has already been created
    if (store[id]) {
        // if yes, return that
        return store[id];
    }
    // otherwise, store this instance
    store[id] = this;
}

 

To nije neki nov obrazac: to je samo Factory Method Pattern uvijen i predstavljen kao konstruktor. Može se napisati kao Sound.create({id: 123}), ali pošto Javascript daje nama ove ekspresivne sposobnosti, ima smisla.

Dakle, ova karakteristika znači da je veoma jednostavno deliti poglede na istoj istanci modela a da se ne zna o drugim pogledima, već samo pozivanjem konstktora sa jednim id. Onda možemo da koritsimo deljene istance ‘event bus’da bi smo olakšali komunikaciju i sinhronizaciju pogleda, Obično to je forum slučanja promena na modelu. Ako je pogled podložan promenama i ‘change’ događaju koji na njega utiče, onda će prvenstveno biti obavešten i stranica se mora ažurirati uz malo napora koji zahteva stvaraoc.

To je način na koji rešavamo pitanja kada nema ni jednog podatka o modelu. Kod prvog prolaza, nekoliko pogleda mogu da imaju referencu modela koji samo sadrži id i nikakav drugi atribut. Kada se dođe do raspodele prvog pogleda, može se uočiti da pogled nema dovoljno informacija, tako da će se model pitati kada da pridobije podatke od API. Model prati ove zahteve, tako da kada drugi modeli takođe zatraže da budu uhvaćeni, ne radi se nište i izbegavaju se duplikati pitanja. Kada se podaci vrate sa servera, atributi modela će se sami ažurirati, dovodeći do promena ‘change’ događaja, pri čemu se svi pogledi o tome obavešatavaju.

Iskorišćavanje podataka u potpunosti

Uobičajena karakteristika mnogih API jeste da kada se zahteva jedan određeni izvor onda odgovarajući izvori koji su slični jesu uključeni u odgovor. Na primer, kod SoundCloud, kada zahtevate informaciju o zvuku, koji je uključen u odgovor u reprezentaciji korisnika koji stvara taj zvuk.

/* api.soundcloud.com/tracks/49931 */
{
  "id": 49931,
  "title": "Hobnotropic",
  ...
  "user": {
    "id": 1433,
    "permalink": "matas",
    "username": "matas",
    "avatar_url": "http://i1.soundc..."
  }
}

 

Hajde da ispustimo ove extra podatke da propadnu, svaki model je svestan koje određene ‘sub-resources’ može da očekuje u odgovoru. Ovi podizvori su uključeni u određene istance radnje uslučaju da bilo koji pogled bi trebalo da koristi podatke. To znači da mi možemo da sačuvamo mnogo dodatnih naznaka kada je u pitanju API i da pokažemo poglede mnogo brže.

Dakle, za nač gore primer, Sound model bi trebalo da zna da je vlasništvo “user” i da ima reprezenaciju tog odgovarajućeg User modela. Kada se dođe do tih podataka, onda dva modela se naprave i napune klijentime na toj određenoj strani za klijente:

var sound = new Sound({id: 49931 });

sound
  .fetch()             // get the data
  .done(function () {  // and when it's done
    var user = new User({id: sound.user.id });
    user.get('username'); // 'matas' -- we already have the model data
  });

 

Ono što je važno da zapamtite je da zato što ima samo jedna istanca svakog modela, čak i prethodne instance jesu ažurirane. Evo primera odozgo, ali imajte na umu to kada se User.

var sound = new Sound({id: 49931 }),
    user = new User({id: 1433 });

user.get('username'); // undefined -- we haven't fetched anything yet.
sound
  .fetch()
  .done(function () {
    user.get('username'); // 'matas' -- the previous instance is updated
  });

Ostvarivanje

Nije izvodljivo da se na svakom modelu zauvek oslanjamo, naročito kada je u pitanju Next SoundCloud. Zbog prirode sajta, moguće je da korisnik može da radi nekoliko sati hours bez prestanka i da ne mora da učita odgovarajuću stranicu. Za vreme tog vremena nastaviće se konzumiranje memorije te aplikacije, koje će vremenom biti sve veće i veće. Zato, u nekom trenutku moramo da podvučemo liniju kod modela koji nisu jednopotezni u odgovarajućem trenutku. Da bi smo odredili kada je sigurno da to uradimo, trenutno čuvanje inkriminiše broj korisnika svakog trenutka kada se zahteva podatak, a pogledi se mogu ‘release’ model kada više nema potrebu i kada se ne dolazi do brojanja.

Periodično, pregledamo zalihe da bi smo videli da li neki modeli imaju zbir nula, i da li su povučeni iz radnje, dozvoljavajući pretraživaču da sakupi slobodnu, upotrebljivu memoriju. Ovaj brojač potrošača se čuva u zbirnom odeljenju, ali u suštini izgleda nešto nalik ovome:

var store = {},
    counts = {};

function Sound(attributes) {
  var id = attributes.id;
  if (store[id]) {
    counts[id]++;
    return store[id];
  }
  store[id] = this;
  counts[id] = 1;
}

Sound.prototype.release = function () {
  counts[this.id]--;
}

 

Razlog izvođenja čišenja tajmera, pree nego bilo koje korišćenje brojača koji je nulti, jeste to jer model ostaje u čuvanju kada pređete na poglede. Ako idete na drugu stranicu, postojaće momenat izmeču čišćenja postojećih pogleda i podešavanja novih kada je zbir svakog mogućeg modela nula. Nova stranica bi možda mogla da sadrži jedan ili više modela, tako da bi bilo traćenje vremena da se svi oni odmah uklone.

Daleki put...

Ovo je kratak uvod u ono što neke metode podrazumevaju i u koncept koji smo ovde koristili da bi smo napravili Next SoundCloud , ali to je samo početak. Ima još nebrojeno karakteristika koje se još moraju napraviti i zato još bezbroj izazova koji se moraju pročačkati. Ako želite da nam se, na ovom dugom putu, priključite, zapamtite da uvek zapošlajavamo nove saradnike !





Published (Last edited): 20-03-2013 , source: http://backstage.soundcloud.com/2012/06/building-the-next-soundcloud/