
Επιτρέψτε μου να σας πω μια τεχνική ιστορία.
Πριν από πολλά χρόνια, ανέπτυξα μια εφαρμογή με ενσωματωμένες λειτουργίες συνεργασίας. Ήταν μια φιλική προς το χρήστη πειραματική στοίβα που αξιοποίησε πλήρως τις δυνατότητες του πρώιμου React και του CouchDB. Συγχρόνισε δεδομένα σε πραγματικό χρόνο μέσω JSON . Χρησιμοποιήθηκε εσωτερικά εντός της εταιρείας, αλλά η ευρεία εφαρμογή του και οι δυνατότητές του σε άλλους τομείς ήταν προφανείς.
Ενώ προσπαθούσαμε να πουλήσουμε αυτήν την τεχνολογία σε πιθανούς πελάτες, συναντήσαμε ένα απροσδόκητο εμπόδιο. Στο βίντεο επίδειξης η τεχνολογία μας φαινόταν και λειτουργούσε άψογα, χωρίς προβλήματα. Το βίντεο έδειξε ακριβώς πώς λειτουργεί και δεν προσομοίωσε τίποτα. Σκεφτήκαμε και κωδικοποιήσαμε ένα ρεαλιστικό σενάριο για τη χρήση του προγράμματος.

Στην πραγματικότητα, αυτό έγινε πρόβλημα. Η επίδειξή μας λειτούργησε ακριβώς με τον τρόπο που όλοι οι άλλοι προσομοίωσαν τις εφαρμογές τους. Συγκεκριμένα, οι πληροφορίες μεταφέρονταν άμεσα από το Α στο Β, ακόμα κι αν επρόκειτο για μεγάλα αρχεία πολυμέσων. Μετά τη σύνδεση, κάθε χρήστης έβλεπε νέες καταχωρήσεις. Με τη βοήθεια της εφαρμογής, διαφορετικοί χρήστες θα μπορούσαν να συνεργαστούν με σαφήνεια στα ίδια έργα, ακόμη και σε περίπτωση διακοπτόμενης σύνδεσης στο Διαδίκτυο κάπου στο χωριό. Αυτό υπονοείται έμμεσα σε κάθε βίντεο προϊόντος που έχει κοπεί στο After Effects.
Ενώ όλοι γνώριζαν σε τι χρησίμευε το κουμπί Ανανέωση, κανείς δεν καταλάβαινε πραγματικά ότι οι εφαρμογές ιστού που μας ζητούσαν να δημιουργήσουμε συνήθως υπόκεινταν στους δικούς τους περιορισμούς. Και αν δεν χρειάζονται πλέον, η εμπειρία χρήστη θα είναι εντελώς διαφορετική. Κυρίως παρατήρησαν ότι μπορούσες να «συνομιλήσεις» αφήνοντας σημειώματα με τους συνομιλητές σου, οπότε αναρωτήθηκαν πώς αυτό διέφερε από, ας πούμε, το Slack. Φτου!
Σχεδιασμός Καθημερινών Συγχρονισμών
Αν έχετε κάποια εμπειρία στην ανάπτυξη λογισμικού, σίγουρα θα σας αγχώσει να θυμάστε ότι οι περισσότεροι άνθρωποι δεν μπορούν απλώς να δουν μια εικόνα μιας διεπαφής και να καταλάβουν τι θα κάνει όταν αλληλεπιδρούν με αυτήν. Για να μην αναφέρουμε τι συμβαίνει μέσα στο ίδιο το πρόγραμμα. Γνωρίζοντας ότι κουτί Το να συμβεί είναι σε μεγάλο βαθμό αποτέλεσμα της γνώσης του τι δεν μπορεί να συμβεί και τι δεν πρέπει να συμβεί. Αυτό απαιτεί όχι μόνο τι κάνει το λογισμικό, αλλά και πώς τα επιμέρους μέρη του συντονίζονται και επικοινωνούν μεταξύ τους.
Ένα κλασικό παράδειγμα αυτού είναι ένας χρήστης που παρακολουθεί ένα βίντεο για είκοσι λεπτά. spinner.gif, αναρωτώμενος πότε θα τελείωνε επιτέλους το έργο. Ο προγραμματιστής θα συνειδητοποιούσε ότι η διαδικασία πιθανότατα είχε παγώσει και ότι το gif δεν θα εξαφανιζόταν ποτέ από την οθόνη. Αυτή η κινούμενη εικόνα προσομοιώνει την εργασία που εκτελείται, αλλά δεν σχετίζεται με την κατάστασή της. Σε περιπτώσεις όπως αυτές, ορισμένοι τεχνικοί αρέσκονται να κάνουν τα στραβά μάτια για το μέγεθος της σύγχυσης των χρηστών. Αλλά προσέξτε ποιο από αυτά δείχνει το περιστρεφόμενο ρολόι και λέει ότι στην πραγματικότητα στέκεται ακίνητο;

Αυτή είναι η ουσία της αξίας του πραγματικού χρόνου. Σήμερα, οι βάσεις δεδομένων πραγματικού χρόνου εξακολουθούν να χρησιμοποιούνται ελάχιστα και αντιμετωπίζονται με καχυποψία από πολλούς. Οι περισσότερες από αυτές τις βάσεις δεδομένων τείνουν σε μεγάλο βαθμό προς το στυλ NoSQL, γι' αυτό και συνήθως χρησιμοποιούν λύσεις που βασίζονται στο Mongo, τις οποίες καλό είναι να ξεχνάμε. Αλλά για μένα, σημαίνει να εξοικειωθώ με το CouchDB και να μάθω πώς να σχεδιάζω δομές που μπορούν να συμπληρώσουν με δεδομένα κάτι περισσότερο από ένας απλός γραφειοκράτης. Νομίζω ότι αξιοποιώ τον χρόνο μου πιο αποτελεσματικά.
Αλλά το πραγματικό θέμα αυτής της ανάρτησης είναι αυτό που χρησιμοποιώ σήμερα. Όχι από επιλογή, αλλά λόγω της αδιάφορα και τυφλά εφαρμοζόμενης εταιρικής πολιτικής. Θα σας δώσω, λοιπόν, μια εντελώς ειλικρινή και αμερόληπτη σύγκριση δύο στενά συνδεδεμένων προϊόντων βάσεων δεδομένων πραγματικού χρόνου της Google.

Και οι δύο έχουν τη λέξη Φωτιά στα ονόματά τους. Ένα πράγμα θυμάμαι με τρυφερότητα. Το δεύτερο πράγμα για μένα είναι ένα διαφορετικό είδος φωτιάς. Δεν βιάζομαι να τους κατονομάσω, γιατί μόλις το κάνω, θα αντιμετωπίσουμε το πρώτο μεγάλο πρόβλημα - τα ονόματα.
Το πρώτο ονομάζεται Βάση δεδομένων Firebase σε πραγματικό χρόνοκαι δεύτερον - Firebase Cloud Firestore. Και τα δύο είναι προϊόντα του Σουίτα Firebase Google. Τα API τους ονομάζονται ανάλογα, firebase.database(…) и firebase.firestore(…).
Συνέβη έτσι επειδή Βάση δεδομένων σε πραγματικό χρόνο - είναι απλώς το πρωτότυπο Firebase μέχρι την αγορά του από την Google το 2014. Στη συνέχεια, η Google αποφάσισε να δημιουργήσει ένα παράλληλο προϊόν αντίγραφο Το Firebase βασίζεται στα μεγάλα δεδομένα της εταιρείας και το ονόμασαν Firestore με cloud. Ελπίζω να μην μπερδεύτηκες ακόμα. Αν εξακολουθείτε να μπερδεύεστε, μην ανησυχείτε, ξαναέγραψα αυτό το μέρος του άρθρου δέκα φορές ο ίδιος.
Επειδή πρέπει να διευκρινιστεί Firebase στην ερώτηση σχετικά με το Firebase, και Firestore στην ερώτηση για το Firebase, τουλάχιστον για να καταλάβεις πριν από μερικά χρόνια στο Stack Overflow.
Αν υπήρχε βραβείο για τη χειρότερη ονομασία λογισμικού, αυτό σίγουρα θα ήταν υποψήφιο. Η απόσταση Χάμινγκ μεταξύ αυτών των ονομάτων είναι τόσο μικρή που μπερδεύει ακόμη και έμπειρους μηχανικούς, των οποίων τα δάχτυλα πληκτρολογούν ένα όνομα ενώ το μυαλό τους σκέφτεται ένα άλλο. Αυτά είναι καλοπροαίρετα σχέδια που έχουν αποτύχει παταγωδώς. Εκπλήρωσαν την προφητεία ότι η βάση δεδομένων θα έπιανε φωτιά. Και δεν αστειεύομαι καθόλου. Το άτομο που σκέφτηκε αυτό το σχέδιο ονοματοδοσίας έχει προκαλέσει αίμα, ιδρώτα και δάκρυα.

πύρρειος νίκη
Μπορεί να πιστεύετε ότι το Firestore είναι αντικατάσταση Firebase, ο απόγονος της επόμενης γενιάς, αλλά αυτό θα ήταν παραπλανητικό. Το Firestore σίγουρα δεν αποτελεί κατάλληλη αντικατάσταση για το Firebase. Φαίνεται σαν κάποιος να έκοψε όλα τα ενδιαφέροντα από αυτό και να μπέρδεψε τα περισσότερα από τα υπόλοιπα με διάφορους τρόπους.
Ωστόσο, μια γρήγορη ματιά στα δύο προϊόντα μπορεί να προκαλέσει σύγχυση: φαίνεται να κάνουν το ίδιο πράγμα, μέσω κυρίως των ίδιων API και ακόμη και στην ίδια συνεδρία βάσης δεδομένων. Οι διαφορές είναι ανεπαίσθητες και αποκαλύπτονται μόνο μέσω προσεκτικής συγκριτικής μελέτης εκτενούς τεκμηρίωσης. Ή όταν προσπαθείτε να μεταφέρετε κώδικα που λειτουργεί τέλεια στο Firebase σε Firestore. Ακόμα και τότε ανακαλύπτετε ότι η διεπαφή της βάσης δεδομένων ανάβει μόλις προσπαθήσετε να κάνετε μεταφορά και απόθεση σε πραγματικό χρόνο. Επιτρέψτε μου να επαναλάβω, δεν αστειεύομαι.
Το πρόγραμμα-πελάτης Firebase είναι ευγενικό, καθώς αποθηκεύει τις αλλαγές στην προσωρινή μνήμη και επαναλαμβάνει αυτόματα τις ενημερώσεις που δίνουν προτεραιότητα στην πιο πρόσφατη εγγραφή. Ωστόσο, το Firestore έχει όριο 1 λειτουργίας εγγραφής ανά έγγραφο ανά χρήστη ανά δευτερόλεπτο και αυτό το όριο επιβάλλεται από τον διακομιστή. Όταν εργάζεστε με αυτό, πρέπει να βρείτε έναν τρόπο να το παρακάμψετε και να εφαρμόσετε έναν περιοριστή ρυθμού ανανέωσης, ακόμα και όταν απλώς προσπαθείτε να δημιουργήσετε την εφαρμογή σας. Δηλαδή, το Firestore είναι μια βάση δεδομένων πραγματικού χρόνου χωρίς πελάτη πραγματικού χρόνου, μεταμφιεσμένη ως μία που χρησιμοποιεί ένα API.
Εδώ είναι που αρχίζουμε να βλέπουμε τα πρώτα σημάδια του λόγου ύπαρξης της Firestore. Μπορεί να κάνω λάθος, αλλά υποψιάζομαι ότι κάποιος υψηλόβαθμος στην Google κοίταξε το Firebase μετά την αγορά και απλώς είπε: «Όχι, Θεέ μου, όχι. Αυτό είναι απαράδεκτο. Απλώς όχι υπό την ηγεσία μου».

Βγήκε από το δωμάτιό του και διακήρυξε:
"Ένα μεγάλο έγγραφο JSON; Όχι. Θα χωρίσετε τα δεδομένα σε ξεχωριστά έγγραφα, το καθένα από τα οποία δεν θα έχει μέγεθος μεγαλύτερο από 1 megabyte."
Φαίνεται ότι ένας τέτοιος περιορισμός δεν θα επιβίωνε μετά την πρώτη του συνάντηση με οποιαδήποτε επαρκώς παρακινημένη βάση χρηστών. Ξέρεις ότι έτσι είναι. Στη δουλειά μας, για παράδειγμα, έχουμε περισσότερες από ενάμιση χιλιάδες παρουσιάσεις, και αυτό είναι απολύτως φυσιολογικό.
Με αυτόν τον περιορισμό, είστε αναγκασμένοι να αποδεχτείτε το γεγονός ότι ένα μόνο "έγγραφο" στη βάση δεδομένων δεν θα μοιάζει με κανένα αντικείμενο που ένας χρήστης θα μπορούσε να ονομάσει έγγραφο.
«Πίνακες πινάκων που μπορούν να περιέχουν αναδρομικά άλλα στοιχεία; Όχι. Οι πίνακες θα περιέχουν μόνο αντικείμενα ή αριθμούς σταθερού μήκους, όπως σκόπευε ο Θεός.»
Έτσι, αν ελπίζατε να βάλετε το GeoJSON στο Firestore σας, θα διαπιστώσετε ότι δεν είναι δυνατό. Τίποτα μη μονοδιάστατο δεν επιτρέπεται. Ελπίζω να σας αρέσει το Base64 ή/και το JSON μέσα σε JSON.
«Εισαγωγή και εξαγωγή JSON μέσω HTTP, εργαλείων γραμμής εντολών ή πίνακα διαχείρισης; Όχι. Θα μπορείτε να εξάγετε και να εισάγετε δεδομένα μόνο στο Google Cloud Storage. Έτσι λέγεται τώρα, νομίζω. Και όταν λέω «εσείς», απευθύνομαι μόνο σε όσους έχουν την εξουσιοδότηση του Κατόχου Έργου. Όλοι οι άλλοι μπορούν να δημιουργήσουν εισιτήρια.»
Όπως μπορείτε να δείτε, το μοντέλο δεδομένων FireBase είναι εύκολο να περιγραφεί. Περιέχει ένα τεράστιο έγγραφο JSON που αντιστοιχίζει κλειδιά JSON σε διαδρομές URL. Αν γράφετε χρησιμοποιώντας HTTP PUT в / Το FireBase έχει ως εξής:
{
"hello": "world"
}
Αυτός GET /hello θα επιστρέψει "world". Βασικά λειτουργεί ακριβώς όπως θα περιμένατε. Συλλογή αντικειμένων FireBase /my-collection/:id ισοδύναμο με ένα λεξικό JSON {"my-collection": {...}} στη ρίζα, το περιεχόμενο του οποίου είναι διαθέσιμο σε /my-collection:
{
"id1": {...object},
"id2": {...object},
"id3": {...object},
// ...
}
Αυτό λειτουργεί καλά αν κάθε ένθετο έχει ένα ID χωρίς συγκρούσεις, για το οποίο το σύστημα έχει μια τυπική λύση.
Με άλλα λόγια, η βάση δεδομένων είναι 100% συμβατή με JSON(*) και λειτουργεί άψογα με HTTP, όπως το CouchDB. Αλλά κυρίως το χρησιμοποιείτε μέσω ενός API πραγματικού χρόνου που αφαιρεί τα websockets, τον έλεγχο ταυτότητας και τις συνδρομές. Ο πίνακας διαχειριστή έχει και τις δύο δυνατότητες, επιτρέποντας τόσο την επεξεργασία σε πραγματικό χρόνο όσο και την εισαγωγή/εξαγωγή JSON. Αν ακολουθήσετε το ίδιο μοτίβο στον κώδικά σας, θα εκπλαγείτε με το πόσο πολύ εξειδικευμένος κώδικας εξαφανίζεται όταν συνειδητοποιήσετε ότι το patch και το diff JSON μπορούν να λύσουν το 90% των συνηθισμένων εργασιών χειρισμού μόνιμης κατάστασης.
Το μοντέλο δεδομένων του Firestore είναι παρόμοιο με το JSON, αλλά διαφέρει σε ορισμένους κρίσιμους τρόπους. Έχω ήδη αναφέρει την έλλειψη πινάκων μέσα σε πίνακες. Το μοντέλο για τις υποσυλλογές είναι ότι αποτελούν έννοιες πρώτης κατηγορίας, ξεχωριστές από το έγγραφο JSON που τις περιέχει. Δεδομένου ότι δεν υπάρχει έτοιμη σειριοποίηση για αυτό, απαιτείται μια εξειδικευμένη διαδρομή κώδικα για την ανάκτηση και την εγγραφή δεδομένων. Για να επεξεργαστείτε τις δικές σας συλλογές, πρέπει να γράψετε τα δικά σας σενάρια και εργαλεία. Ο πίνακας διαχειριστή σάς επιτρέπει να κάνετε μόνο μικρές αλλαγές σε ένα πεδίο κάθε φορά και δεν διαθέτει δυνατότητες εισαγωγής/εξαγωγής.
Πήραν μια βάση δεδομένων NoSQL πραγματικού χρόνου και την μετέτρεψαν σε μια αργή μη-SQL με αυτόματες ενώσεις και ξεχωριστή στήλη μη-JSON. Κάτι σαν το GraftQL.

Ζεστή Ιάβα
Αν το Firestore υποτίθεται ότι ήταν πιο ισχυρό και επεκτάσιμο, η ειρωνεία είναι ότι ο μέσος προγραμματιστής θα καταλήξει με μια λιγότερο ισχυρή λύση από ό,τι αν επέλεγε το FireBase αμέσως. Το είδος του λογισμικού που χρειάζεται ο Grumpy Database Administrator απαιτεί ένα επίπεδο προσπάθειας και προσόντων από ανθρώπους που είναι απλώς μη ρεαλιστικό για την εξειδικευμένη αγορά στην οποία υποτίθεται ότι είναι καλό το προϊόν. Είναι παρόμοιο με το πώς το HTML5 Canvas δεν αντικαθιστά καθόλου το Flash χωρίς τα εργαλεία ανάπτυξης και το πρόγραμμα αναπαραγωγής. Επιπλέον, το Firestore έχει βαλτώσει σε μια επιθυμία για καθαρότητα δεδομένων και στείρα επικύρωση που απλά δεν ευθυγραμμίζεται με τον τρόπο που ο μέσος χρήστης της επιχείρησης... λατρεύει να εργάζεται: για αυτόν, όλα είναι προαιρετικά, γιατί μέχρι το τέλος, όλα είναι ένα προσχέδιο.
Το κύριο μειονέκτημα του FireBase είναι ότι ο πελάτης δημιουργήθηκε αρκετά χρόνια νωρίτερα από την εποχή του, πριν οι περισσότεροι προγραμματιστές ιστοσελίδων γνωρίζουν την αμετάβλητη φύση. Εξαιτίας αυτού, το FireBase υποθέτει ότι θα αλλάζετε τα δεδομένα και επομένως δεν εκμεταλλεύεται την αμετάβλητη δυνατότητα που παρέχεται από τον χρήστη. Επιπλέον, δεν επαναχρησιμοποιεί δεδομένα στα στιγμιότυπα που παρέχει στον χρήστη, καθιστώντας τις διαφορές πολύ πιο δύσκολες στην εκτέλεση. Για μεγάλα έγγραφα, ο μηχανισμός συναλλαγών που βασίζεται σε μεταβλητές διαφορές είναι απλώς ανεπαρκής. Παιδιά, το έχουμε ήδη... WeakMap σε JavaScript. Είναι βολικό.
Αν διαμορφώσετε σωστά τα δεδομένα και δεν κάνετε τα δέντρα πολύ ογκώδη, μπορείτε να παρακάμψετε αυτό το πρόβλημα. Αλλά αναρωτιέμαι αν το FireBase θα ήταν πολύ πιο ενδιαφέρον αν οι προγραμματιστές κυκλοφορούσαν ένα πραγματικά καλό API πελάτη που χρησιμοποιούσε την αμετάβλητη λειτουργία σε συνδυασμό με κάποιες σοβαρές πρακτικές συμβουλές για το σχεδιασμό βάσεων δεδομένων. Αντίθετα, φαίνεται ότι προσπάθησαν να επισκευάσουν κάτι που δεν ήταν σπασμένο, και αυτό το έκανε χειρότερο.
Δεν γνωρίζω την πλήρη λογική πίσω από τη δημιουργία του Firestore. Οι εικασίες σχετικά με τα κίνητρα που προκύπτουν μέσα στο μαύρο κουτί είναι επίσης μέρος της διασκέδασης. Αυτή η αντιπαράθεση δύο εξαιρετικά παρόμοιων αλλά ασύγκριτων βάσεων δεδομένων είναι αρκετά σπάνια. Είναι σαν κάποιος να σκέφτηκε: "Το Firebase είναι απλώς μια λειτουργία που μπορούμε να μιμηθούμε στο Google Cloud", αλλά δεν είχε ανακαλύψει ακόμη την έννοια του εντοπισμού απαιτήσεων του πραγματικού κόσμου ή της δημιουργίας χρήσιμων λύσεων που ικανοποιούν όλες αυτές τις απαιτήσεις. «Ας το σκεφτούν οι προγραμματιστές. Απλώς κάντε το UI όμορφο... Μπορούμε να προσθέσουμε περισσότερη φωτιά;»
Καταλαβαίνω μερικά πράγματα σχετικά με τις δομές δεδομένων. Σίγουρα βλέπω την έννοια «τα πάντα σε ένα μεγάλο δέντρο JSON» ως μια προσπάθεια αφαίρεσης οποιασδήποτε αίσθησης δομής μεγάλης κλίμακας από τη βάση δεδομένων. Το να περιμένουμε από ένα λογισμικό να χειρίζεται απλώς οποιοδήποτε αμφίβολης δομής δεδομένων φράκταλ είναι απλώς τρελό. Δεν χρειάζεται καν να φανταστώ πόσο άσχημα θα μπορούσε να είναι, έχω κάνει αυστηρούς ελέγχους κώδικα και Είδα πράγματα που εσείς ούτε καν ονειρευτήκατε. Αλλά ξέρω επίσης πώς μοιάζουν οι καλές δομές, и . Μπορώ να φανταστώ έναν κόσμο όπου το Firestore φαινόταν λογικό να ταιριάζει και οι άνθρωποι που το δημιούργησαν ένιωθαν ότι έκαναν καλή δουλειά. Αλλά δεν ζούμε σε αυτόν τον κόσμο.
Η υποστήριξη δημιουργίας ερωτημάτων του FireBase είναι κακή από κάθε άποψη και είναι σχεδόν ανύπαρκτη. Σίγουρα χρειάζεται βελτίωση ή έστω αναθεώρηση. Αλλά το Firestore δεν είναι πολύ καλύτερο, καθώς περιορίζεται στα ίδια μονοδιάστατα ευρετήρια που βρίσκονται σε απλό SQL. Αν θέλετε τα ερωτήματα που εκτελούν οι χρήστες σε χαοτικά δεδομένα, χρειάζεστε αναζήτηση πλήρους κειμένου, φίλτρα πολλαπλών εύρων και σειρά που ορίζεται από τον χρήστη. Όταν εξεταστούν προσεκτικά, οι λειτουργίες της απλής SQL είναι πολύ περιορισμένες. Επίσης, τα μόνα ερωτήματα SQL που μπορούν να εκτελέσουν οι χρήστες στην παραγωγή είναι τα γρήγορα ερωτήματα. Θα χρειαστείτε μια ειδική λύση δημιουργίας ευρετηρίου με καλά σχεδιασμένες δομές δεδομένων. Για όλα τα υπόλοιπα θα πρέπει τουλάχιστον να υπάρχει μια σταδιακή μείωση χάρτη ή κάτι παρόμοιο.
Αν ψάξετε στα έγγραφα της Google για αυτό, ελπίζουμε ότι θα σας κατευθύνουν προς κάτι όπως το BigTable και το BigQuery. Ωστόσο, όλες αυτές οι λύσεις συνοδεύονται από τόσο πυκνή ορολογία εταιρικών πωλήσεων που γρήγορα θα γυρίσετε πίσω και θα αρχίσετε να ψάχνετε για κάτι άλλο.
Το τελευταίο πράγμα που θέλετε σε μια βάση δεδομένων πραγματικού χρόνου είναι κάτι που δημιουργείται από και για άτομα που εργάζονται στην κλίμακα μισθοδοσίας της διοίκησης.
(*) Αυτό είναι αστείο, δεν υπάρχει κάτι τέτοιο .
Σχετικά με τα Δικαιώματα Διαφήμισης
Ψάχνεις για Για τον εντοπισμό σφαλμάτων έργου, έναν διακομιστή για ανάπτυξη και φιλοξενία; Είστε σίγουρα πελάτης μας 🙂 Ημερήσια τιμολόγηση για διακομιστές διαφόρων διαμορφώσεων, anti-DDoS και άδειες χρήσης Windows περιλαμβάνονται ήδη στην τιμή.
Πηγή: www.habr.com
