Zrozumienie publikowania / subskrypcji Meteor

84

Mam prostą aplikację, która wyświetla listę plików Projects. Usunąłem autopublishpaczkę, więc nie wysyłam wszystkiego do klienta.

 <template name="projectsIndex">    
   {{#each projects}}      
     {{name}}
   {{/each}}
 </template>

Gdy autopublishzostało włączone, wyświetli się wszystkie projekty:

if Meteor.isClient
  Template.projectsIndex.projects = Projects.find()

Po usunięciu muszę dodatkowo wykonać:

 if Meteor.isServer
   Meteor.publish "projects", ->
     Projects.find()
 if Meteor.isClient
   Meteor.subscribe "projects"
   Template.projectsIndex.projects = Projects.find()

Czy zatem słuszne jest stwierdzenie, że find()metoda po stronie klienta przeszukuje tylko rekordy, które zostały opublikowane po stronie serwera? Drażniło mnie to, bo czułem, że powinienem zadzwonić tylko find()raz.

DVG
źródło

Odpowiedzi:

286

Kolekcje, publikacje i subskrypcje to trudny obszar Meteor, który dokumentacja może omówić bardziej szczegółowo, aby uniknąć częstego zamieszania , które czasami jest wzmacniane przez mylącą terminologię .

Oto Sacha Greif (współautor DiscoverMeteor ) wyjaśniający publikacje i subskrypcje na jednym slajdzie:

subskrypcje

Aby właściwie zrozumieć, dlaczego musisz dzwonić find()więcej niż raz, musisz zrozumieć, jak działają kolekcje, publikacje i subskrypcje w Meteor:

  1. Kolekcje definiujesz w MongoDB. Żaden Meteor nie był jeszcze zaangażowany. Te kolekcje zawierają rekordy bazy danych (nazywane również „dokumentami” zarówno przez Mongo, jak i Meteor , ale „dokument” jest bardziej ogólny niż rekord bazy danych; na przykład specyfikacja aktualizacji lub selektor zapytania to również dokumenty - obiekty JavaScript zawierające field: valuepary).

  2. Następnie definiujesz kolekcje na serwerze Meteor za pomocą

    MyCollection = new Mongo.Collection('collection-name-in-mongo')
    

    Te kolekcje zawierają wszystkie dane z kolekcji MongoDB i można MyCollection.find({...})na nich uruchomić , co zwróci kursor (zestaw rekordów z metodami do ich iteracji i zwrócenia).

  3. Kursor ten jest (przez większość czasu) używany do publikowania (wysyłania) zestawu rekordów (zwanego „zestawem rekordów” ). Opcjonalnie możesz opublikować tylko niektóre pola z tych rekordów. To zestawy rekordów ( nie kolekcje) subskrybują klienci . Publikowanie odbywa się za pomocą funkcji publikowania , która jest wywoływana za każdym razem, gdy subskrybuje nowy klient, i która może przyjmować parametry do zarządzania, które rekordy mają zwrócić (np. Identyfikator użytkownika, aby zwrócić tylko dokumenty tego użytkownika).

  4. Na kliencie masz kolekcje Minimongo, które częściowo odzwierciedlają niektóre rekordy z serwera. „Częściowo”, ponieważ mogą zawierać tylko niektóre pola i „niektóre rekordy”, ponieważ zazwyczaj chcesz wysłać klientowi tylko te rekordy, których potrzebuje, aby przyspieszyć ładowanie strony, i tylko te, których potrzebuje i do których ma uprawnienia dostęp.

    Minimongo jest zasadniczo nietrwałą implementacją Mongo w pamięci, w czystym języku JavaScript. Służy jako lokalna pamięć podręczna, która przechowuje tylko podzbiór bazy danych, z którą pracuje ten klient. Zapytania na kliencie (find) są obsługiwane bezpośrednio z tej pamięci podręcznej, bez komunikacji z serwerem.

    Te kolekcje Minimongo są początkowo puste. Są wypełnione

    Meteor.subscribe('record-set-name')
    

    wezwania. Zauważ, że parametr subskrypcji nie jest nazwą kolekcji; jest to nazwa zestawu rekordów , którego serwer użył w publishwywołaniu. subscribe()Wezwanie subskrybuje klienta do zestawu rekord - podzbiór rekordów z kolekcji serwera (np najnowsze posty na blogach 100), przy czym wszystkie lub podzbiór pól w każdym rekordzie (na przykład tylko titlei date). Skąd Minimongo wie, w której kolekcji umieścić przychodzące rekordy? Nazwa kolekcji będzie collectionargumentem w Publish przewodnika added, changedi removedwywołania zwrotne, lub jeśli takowych brak (co ma miejsce w większości przypadków), będzie to nazwa kolekcji MongoDB na serwerze.

Modyfikowanie rekordów

W tym miejscu Meteor sprawia, że ​​jest to bardzo wygodne: kiedy zmodyfikujesz rekord (dokument) w kolekcji Minimongo na kliencie, Meteor natychmiast zaktualizuje wszystkie szablony, które od niego zależą, a także wyśle ​​zmiany z powrotem na serwer, który z kolei zapisze zmiany w MongoDB i wyśle ​​je do odpowiednich klientów, którzy subskrybowali zestaw rekordów zawierający ten dokument. Nazywa się to kompensacją latencji i jest jedną z siedmiu podstawowych zasad Meteor .

Wiele subskrypcji

Możesz mieć kilka subskrypcji, które pobierają różne rekordy, ale wszystkie trafią do tej samej kolekcji na kliencie, jeśli pochodzą z tej samej kolekcji na serwerze, na podstawie ich _id. Nie jest to jasno wyjaśnione, ale sugerowane przez dokumentację Meteor:

Kiedy subskrybujesz zestaw rekordów, mówi on serwerowi, aby wysłał rekordy do klienta. W sklepach klienckie te zapisy w miejscowych zbiorów Minimongo, o tej samej nazwie co collectionargumentu użytego w publikowania przewodnika added, changedi removedzwrotnych. Meteor będzie kolejkował przychodzące atrybuty, dopóki nie zadeklarujesz Mongo.Collection na kliencie z pasującą nazwą kolekcji.

Co nie wyjaśnił, co się dzieje, kiedy nie jawnie użyć added, changedi removedczy w ogóle publikować teleskopowe - co jest przez większość czasu. W tym najczęstszym przypadku argument kolekcji jest (co nie dziwi) pobierany z nazwy kolekcji MongoDB, którą zadeklarowałeś na serwerze w kroku 1. Ale oznacza to, że możesz mieć różne publikacje i subskrypcje o różnych nazwach, a wszystkie rekordy trafią do tej samej kolekcji na kliencie. Aż do poziomu pól najwyższego poziomu , Meteor dba o wykonanie określonej unii między dokumentami, tak aby subskrypcje mogły się nakładać - publikuj funkcje, które wysyłają różne pola najwyższego poziomu do klienta, który działa obok siebie i na kliencie, dokument w kolekcja będziepołączenie dwóch zestawów pól .

Przykład: wiele subskrypcji wypełniających tę samą kolekcję na kliencie

Masz kolekcję BlogPosts, którą deklarujesz w ten sam sposób zarówno na serwerze, jak i kliencie, mimo że robi to różne rzeczy:

BlogPosts = new Mongo.Collection('posts');

Na kliencie BlogPostsmożna pobrać rekordy z:

  1. subskrypcja ostatnich 10 postów na blogu

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. subskrypcja postów aktualnego użytkownika

    // server
    Meteor.publish('posts-current-user', function publishFunction() {
      return BlogPosts.find({author: this.userId}, {sort: {date: -1}, limit: 10});
      // this.userId is provided by Meteor - http://docs.meteor.com/#publish_userId
    }
    Meteor.publish('posts-by-user', function publishFunction(who) {
      return BlogPosts.find({authorId: who._id}, {sort: {date: -1}, limit: 10});
    }
    
    // client
    Meteor.subscribe('posts-current-user');
    Meteor.subscribe('posts-by-user', someUser);
    
  3. subskrypcja najpopularniejszych postów

  4. itp.

Wszystkie te dokumenty pochodzą z postskolekcji w MongoDB, za pośrednictwem BlogPostskolekcji na serwerze i trafiają do BlogPostskolekcji na kliencie.

Teraz możemy zrozumieć, dlaczego trzeba dzwonić find()więcej niż jeden raz - za drugim razem u klienta, ponieważ dokumenty ze wszystkich abonamentów trafią do tej samej kolekcji i trzeba będzie pobierać tylko te, na których nam zależy. Na przykład, aby uzyskać najnowsze posty na kliencie, po prostu odzwierciedlasz zapytanie z serwera:

var recentPosts = BlogPosts.find({}, {sort: {date: -1}, limit: 10});

Spowoduje to zwrócenie kursora do wszystkich dokumentów / rekordów, które klient otrzymał do tej pory, zarówno najlepszych postów, jak i postów użytkownika. ( dzięki Geoffrey ).

Dan Dascalescu
źródło
10
To jest świetne. Może warto wspomnieć, co się stanie, jeśli zrobisz BlogPosts.find({})na kliencie po zasubskrybowaniu obu publikacji - tj. Zwróci to kursor wszystkich dokumentów / rekordów znajdujących się obecnie na kliencie, zarówno najlepszych postów, jak i postów użytkownika. Widziałem inne pytania na SO, w których pytający był zdezorientowany.
Geoffrey Booth
3
To jest świetne. dzięki. Ponadto kolekcja Meteor.users () jest nieco zagmatwana, ponieważ jest automatycznie publikowana po stronie klienta. Czy można dodać trochę do powyższej odpowiedzi, aby określić kolekcję users ()?
Jimmy MG Lim
3
Nawet jeśli o wiele więcej niż pierwotnie pytano, myślę, że @DVG powinno oznaczyć ten wspaniały zapis jako zaakceptowaną odpowiedź. Dzięki Dan.
physiocoder
1
Dzięki @DanDascalescu, Świetne wyjaśnienie, które wiele dla mnie wyjaśniło, jedyna rzecz, którą śledząc dokumenty meteor o „kolekcjach” po przeczytaniu twojego wyjaśnienia, myślę, że BlogPostsnie jest to kolekcja, to zwrócony obiekt, który ma metody takie jak „wstaw”, „aktualizacja "..etc, a prawdziwa kolekcja jest również postsw kliencie i serwerze.
UXE
4
Czy można wzywać tylko zestaw rekordów, który subskrybujesz? Tak jak w przypadku, czy możliwe jest bezpośrednie pobranie rekordu ustawionego w moim javascript zamiast lokalnego odpytywania bazy danych Minimongo?
Jimmy Knoot
27

Tak, funkcja find () po stronie klienta zwraca tylko dokumenty znajdujące się na kliencie w Minimongo. Z dokumentów :

Na kliencie tworzona jest instancja Minimongo. Minimongo jest zasadniczo nietrwałą implementacją Mongo w pamięci, w czystym języku JavaScript. Służy jako lokalna pamięć podręczna, która przechowuje tylko podzbiór bazy danych, z którą pracuje ten klient. Zapytania na kliencie (find) są obsługiwane bezpośrednio z tej pamięci podręcznej, bez komunikacji z serwerem.

Jak powiedziałeś, publikuj () określa, jakie dokumenty będzie miał klient.

user728291
źródło
1

Podstawowa zasada kciuka jest tutaj publishi subscribednazwy zmiennych powinny być takie same na klienta i po stronie serwera.

Nazwy kolekcji po stronie Mongo DB i po stronie klienta powinny być takie same.

Załóżmy, że używam opcji publikowania i subskrybowania mojej kolekcji o nazwie employees, jak będzie wyglądał kod


po stronie serwera

Tutaj użycie varsłowa kluczowego jest opcjonalne (użyj tego słowa kluczowego, aby ustawić kolekcję lokalną dla tego pliku).

CollectionNameOnServerSide = new Mongo.Collection('employees');   

Meteor.publish('employeesPubSub', function() { 
    return CollectionNameOnServerSide.find({});     
});

plik js po stronie klienta

CollectionNameOnClientSide = new Mongo.Collection('employees');
var employeesData = Meteor.subscribe('employeesPubSub');

Template.templateName.helpers({
  'subcribedDataNotAvailable' : function(){
        return !employeesData.ready();
    },
   'employeeNumbers' : () =>{
       CollectionNameOnClientSide.find({'empId':1});
  }
});

plik .html po stronie klienta

Tutaj możemy użyć subcribedDataNotAvailablemetody pomocniczej, aby wiedzieć, czy dane są gotowe po stronie klienta, jeśli dane są gotowe, a następnie wydrukować numery pracowników metodą employeeNumberspomocniczą.

<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
   <h1> data loading ... </h1>
 {{else}}
  {{#each employeeNumbers }}
     {{this}}
  {{/each}}
 {{/if}}
<TEMPLATE>
Puneeth Reddy V
źródło
0
// on the server
Meteor.publish('posts', function() {

    return Posts.find();

});

// on the client
Meteor.subscribe('posts');
Shemeer M Ali
źródło