A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Eco-système et stockage générique (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://www.servicedenuages.fr/eco-systeme-et-stockage-generique.html">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Eco-système et stockage générique (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://www.servicedenuages.fr/eco-systeme-et-stockage-generique.html">Source originale du contenu</a></h3>
  445. <p>Comme nous l'écrivions <a class="reference external" href="http://www.servicedenuages.fr/service-de-nuages.html">dans l'article précédent</a>, nous souhaitons construire une
  446. solution de stockage générique.</p>
  447. <p>Notre objectif est simple: permettre aux développeurs d'application, internes
  448. à Mozilla ou du monde entier, de faire persister et synchroniser facilement des
  449. données associées à un utilisateur.</p>
  450. <p id="storage-specs">Les aspects de l'architecture qui nous semblent incontournables:</p>
  451. <ul class="simple">
  452. <li>La solution doit reposer sur un protocole, et non sur une implémentation ;</li>
  453. <li>L'auto-hébergement de l'ensemble doit être simplissime ;</li>
  454. <li>L'authentification doit être <em>pluggable</em>, voire décentralisée (OAuth2, FxA,
  455. Persona) ;</li>
  456. <li>Les enregistrements doivent pouvoir être validés par le serveur ;</li>
  457. <li>Les données doivent pouvoir être stockées dans n'importe quel backend ;</li>
  458. <li>Un système de permissions doit permettre de protéger des collections, ou de
  459. partager des enregistrements de manière fine ;</li>
  460. <li>La résolution de conflits doit pouvoir avoir lieu sur le serveur ;</li>
  461. <li>Le client doit être pensé «<em>offline-first</em>» ;</li>
  462. <li>Le client doit pouvoir réconcilier les données simplement ;</li>
  463. <li>Le client doit pouvoir être utilisé aussi bien dans le navigateur que côté
  464. serveur.</li>
  465. </ul>
  466. <p>La première question qui nous a été posée fût «<em>Pourquoi vous n'utilisez pas
  467. PouchDB ou Remote Storage ?</em>»</p>
  468. <div class="section" id="remote-storage">
  469. <h2>Remote Storage</h2>
  470. <p>Remote Storage est un standard ouvert pour du stockage par utilisateur. <a class="reference external" href="http://tools.ietf.org/html/draft-dejong-remotestorage-04">La
  471. specification</a> se
  472. base sur des standards déjà existants et éprouvés: Webfinger, OAuth 2, CORS et
  473. REST.</p>
  474. <p>L'API est simple, des <a class="reference external" href="http://blog.cozycloud.cc/news/2014/08/12/when-unhosted-meets-cozy-cloud/">projets prestigieux l'utilisent</a>.
  475. Il y a plusieurs <a class="reference external" href="https://github.com/jcoglan/restore">implémentations</a> du
  476. serveur, et il existe <a class="reference external" href="https://www.npmjs.com/package/remotestorage-server">un squelette Node</a> pour construire un
  477. serveur sur mesure.</p>
  478. <img alt="Remote Storage widget" class="align-left" src="http://www.servicedenuages.fr/images/remotestorage-widget.png"/>
  479. <p>Le client <a class="reference external" href="https://github.com/remotestorage/remotestorage.js/">remoteStorage.js</a> permet d'intégrer la
  480. solution dans les applications Web. Il se charge du «store local», du cache,
  481. de la synchronization, et fournit un widget qui permet aux utilisateurs des
  482. applications de choisir le serveur qui recevra les données (via Webfinger).</p>
  483. <p><a class="reference external" href="https://github.com/michielbdejong/ludbud">ludbud</a>, la version épurée de
  484. <em>remoteStorage.js</em>, se limite à l'abstraction du stockage distant. Cela
  485. permettrait à terme, d'avoir une seule bibliothèque pour stocker dans un
  486. serveur <em>remoteStorage</em>, <em>ownCloud</em> ou chez les méchants comme <em>Google Drive</em>
  487. ou <em>Dropbox</em>.</p>
  488. <p>Au premier abord, la spécification correspond à ce que nous voulons accomplir:</p>
  489. <ul class="simple">
  490. <li>La philosophie du protocole est saine;</li>
  491. <li>L'éco-système est bien fichu;</li>
  492. <li>La vision politique colle: redonner le contrôle des données aux utilisateurs
  493. (voir <a class="reference external" href="http://unhosted.org/">unhosted</a>);</li>
  494. <li>Les choix techniques compatibles avec ce qu'on a commencé (CORS, REST, OAuth 2);</li>
  495. </ul>
  496. <p>En revanche, vis à vis de la manipulation des données, il y a plusieurs
  497. différences avec ce que nous souhaitons faire:</p>
  498. <p>En résumé, il semblerait que ce que nous souhaitons faire avec le stockage
  499. d'enregistrements validés est complémentaire avec <em>Remote Storage</em>.</p>
  500. <p>Si des besoin de persistence orientés «fichiers» se présentent, a priori nous aurions tort
  501. de réinventer les solutions apportées par cette spécification. Il y a donc de grandes
  502. chances que nous l´intégrions à terme, et que <em>Remote Storage</em> devienne une facette
  503. de notre service.</p>
  504. </div>
  505. <div class="section" id="pouchdb">
  506. <h2>PouchDB</h2>
  507. <p><a class="reference external" href="http://pouchdb.com/">PouchDB</a> est une bibliothèque JavaScript qui permet
  508. de manipuler des enregistrements en local et de les synchroniser vers une base distante.</p>
  509. <div class="highlight"><pre><span class="kd">var</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PouchDB</span><span class="p">(</span><span class="s1">'dbname'</span><span class="p">);</span>
  510. <span class="nx">db</span><span class="p">.</span><span class="nx">put</span><span class="p">({</span>
  511. <span class="nx">_id</span><span class="o">:</span> <span class="s1">'dave@gmail.com'</span><span class="p">,</span>
  512. <span class="nx">name</span><span class="o">:</span> <span class="s1">'David'</span><span class="p">,</span>
  513. <span class="nx">age</span><span class="o">:</span> <span class="mi">68</span>
  514. <span class="p">});</span>
  515. <span class="nx">db</span><span class="p">.</span><span class="nx">replicate</span><span class="p">.</span><span class="nx">to</span><span class="p">(</span><span class="s1">'http://example.com/mydb'</span><span class="p">);</span>
  516. </pre></div>
  517. <p>Le projet a le vent en poupe, bénéficie de nombreux contributeurs,
  518. l'éco-système est très riche et l'adoption par des projets <a class="reference external" href="https://github.com/hoodiehq/wip-hoodie-store-on-pouchdb">comme Hoodie</a> ne fait que
  519. confirmer la pertinence de l'outil pour les développeurs frontend.</p>
  520. <p><em>PouchDB</em> gère un « store » local, dont la persistence est abstraite et <a class="reference external" href="http://pouchdb.com/2014/07/25/pouchdb-levels-up.html">repose
  521. sur</a> l'API <a class="reference external" href="https://github.com/level/levelup#relationship-to-leveldown">LevelDown</a> pour persister
  522. les données dans <a class="reference external" href="https://github.com/Level/levelup/wiki/Modules#storage-back-ends">n'importe quel backend</a>.</p>
  523. <p>Même si <em>PouchDB</em> adresse principalement les besoins des applications
  524. «<em>offline-first</em>», il peut être utilisé aussi bien dans le navigateur que côté
  525. serveur, via Node.</p>
  526. <div class="section" id="synchronisation">
  527. <h3>Synchronisation</h3>
  528. <p>La synchronisation (ou réplication) des données locales s'effectue sur un
  529. <a class="reference external" href="http://couchdb.apache.org/">CouchDB</a> distant.</p>
  530. <p>Le projet <a class="reference external" href="https://github.com/pouchdb/pouchdb-server">PouchDB Server</a>
  531. implémente l'API de CouchDB en NodeJS. Comme <em>PouchDB</em> est utilisé, on obtient
  532. un service qui se comporte comme un <em>CouchDB</em> mais qui stocke ses données
  533. n'importe où, dans un <em>Redis</em> ou un <em>PostgreSQL</em> par exemple.</p>
  534. <p>La synchronisation est complète. Autrement dit, tous les enregistrements qui
  535. sont sur le serveur se retrouvent synchronisés dans le client. Il est possible
  536. de filtrer les collections synchronisées, mais cela <a class="reference external" href="http://pouchdb.com/2015/04/05/filtered-replication.html">n'a pas pour objectif de
  537. sécuriser l'accès aux données</a>.</p>
  538. <p>L'approche recommandée pour cloisonner les données par utilisateur consiste
  539. à créer <a class="reference external" href="https://github.com/nolanlawson/pouchdb-authentication#some-people-can-read-some-docs-some-people-can-write-those-same-docs">une base de données par utilisateur</a>.</p>
  540. <p>Ce n'est pas forcément un problème, CouchDB <a class="reference external" href="https://mail-archives.apache.org/mod_mbox/couchdb-user/201401.mbox/%3C52CEB873.7080404@ironicdesign.com%3E">supporte des centaines de milliers
  541. de bases sans sourciller</a>.
  542. Mais selon les cas d'utilisation, le cloisement n'est pas toujours facile
  543. à déterminer (par rôle, par application, par collection, ...).</p>
  544. </div>
  545. </div>
  546. <div class="section" id="le-cas-d-utilisation-payments">
  547. <h2>Le cas d'utilisation « Payments »</h2>
  548. <img alt="Put Payments Here -- Before the Internet - CC-NC-SA Katy Silberger https://www.flickr.com/photos/katysilbs/11163812186" src="http://www.servicedenuages.fr/images/put-payments.jpg"/>
  549. <p>Dans les prochaines semaines, nous devrons mettre sur pied un prototype pour
  550. tracer l'historique des paiements et abonnements d'un utilisateur.</p>
  551. <p>Le besoin est simple:</p>
  552. <ul class="simple">
  553. <li>l'application « Payment » enregistre les paiements et abonnements d'un
  554. utilisateur pour une application donnée;</li>
  555. <li>l'application « Donnée » interroge le service pour vérifier qu'un utilisateur
  556. a payé ou est abonné;</li>
  557. <li>l'utilisateur interroge le service pour obtenir la liste de tous ses
  558. abonnements.</li>
  559. </ul>
  560. <p>Seule l'application « Payment » a le droit de créer/modifier/supprimer des
  561. enregistrements, les deux autres ne peuvent que consulter en lecture seule.</p>
  562. <p>Une application donnée ne peut pas accéder aux paiements des autres
  563. applications, et un utilisateur ne peut pas accéder aux paiements des autres
  564. utilisateurs.</p>
  565. <div class="section" id="avec-remotestorage">
  566. <h3>Avec RemoteStorage</h3>
  567. <img alt="Remote Love - CC-BY-NC Julie https://www.flickr.com/photos/mamajulie2008/2609549461" class="align-center" src="http://www.servicedenuages.fr/images/remote-love.jpg"/>
  568. <p>Clairement, l'idée de <em>RemoteStorage</em> est de dissocier l'application executée,
  569. et les données crées par l'utilisateur avec celle-ci.</p>
  570. <p>Dans notre cas, c'est l'application « Payment » qui manipule des données
  571. concernant un utilisateur. Mais celles-ci ne lui appartiennent pas directement:
  572. certes un utilisateur doit pouvoir les supprimer, surtout pas en créer ou les
  573. modifier!</p>
  574. <p>La notion de permissions limitée à privé/publique ne suffit pas dans ce cas
  575. précis.</p>
  576. </div>
  577. <div class="section" id="avec-pouchdb">
  578. <h3>Avec PouchDB</h3>
  579. <p>Il va falloir créer une <em>base de données</em> par utilisateur, afin d'isoler les
  580. enregistrements de façon sécurisée. Seule l'application « Payment » aura tous
  581. les droits sur les databases.</p>
  582. <p>Mais cela ne suffit pas.</p>
  583. <p>Il ne faut pas qu'une application puisse voir les paiements des autres
  584. applications, donc il va aussi falloir recloisonner, et créer une <em>base de
  585. données</em> par application.</p>
  586. <p>Quand un utilisateur voudra accéder à l'ensemble de ses paiements, il faudra
  587. agréger les <em>databases</em> de toutes les applications. Quand l'équipe marketing
  588. voudra faire des statistiques sur l'ensemble des applications, il faudra
  589. agrégér des centaines de milliers de <em>databases</em>.</p>
  590. <p>Ce qui est fort dommage, puisqu'il est probable que les paiements ou
  591. abonnements d'un utilisateur pour une application se comptent sur les doigts
  592. d'une main. Des centaines de milliers de bases contenant moins de
  593. 5 enregistrements ?</p>
  594. <p>De plus, dans le cas de l'application « Payment », le serveur est implémenté en
  595. Python. Utiliser un wrapper JavaScript comme le fait <a class="reference external" href="https://pythonhosted.org/Python-PouchDB/">python-pouchdb</a> cela ne nous fait pas trop rêver.</p>
  596. </div>
  597. </div>
  598. <div class="section" id="un-nouvel-eco-systeme">
  599. <h2>Un nouvel éco-système ?</h2>
  600. <img alt="Wagon wheel - CC-BY-NC-SA arbyreed https://www.flickr.com/photos/19779889@N00/16161808220" src="http://www.servicedenuages.fr/images/wagon-wheel.jpg"/>
  601. <p>Évidemment, quand on voit la richesse des projets <em>PouchDB</em> et <em>Remote Storage</em>
  602. et la dynamique de ces communautés, il est légitime d'hésiter avant de
  603. développer une solution alternative.</p>
  604. <p>Quand nous avons créé le serveur <em>Reading List</em>, nous l'avons construit avec
  605. <a class="reference external" href="http://cliquet.readthedocs.org/">Cliquet</a>, ce fût l'occasion de mettre au
  606. point <a class="reference external" href="http://cliquet.readthedocs.org/en/latest/api/">un protocole très simple</a>, fortement inspiré de
  607. <a class="reference external" href="http://en.wikipedia.org/wiki/Firefox_Sync">Firefox Sync</a>, pour faire de la
  608. synchronisation d'enregistrements.</p>
  609. <p>Et si les clients <em>Reading List</em> ont pu être implémentés en quelques semaines,
  610. que ce soit en JavaScript, Java (Android) et ASM (Add-on Firefox), c'est que le
  611. principe «<em>offline first</em>» du service est trivial.</p>
  612. <div class="section" id="les-compromis">
  613. <h3>Les compromis</h3>
  614. <p>Évidemment, nous n'avons pas la prétention de concurrencer <em>CouchDB</em>. Nous faisons plusieurs
  615. concessions:</p>
  616. <ul class="simple">
  617. <li>De base, les collections d'enregistrements sont cloisonnées par utilisateur;</li>
  618. <li>Pas d'historique des révisions;</li>
  619. <li>Pas de diff sur les enregistrements entre révisions;</li>
  620. <li>De base, pas de résolution de conflit automatique;</li>
  621. <li>Pas de synchronisation par flux (<em>streams</em>);</li>
  622. </ul>
  623. <p>Jusqu'à preuve du contraire, ces compromis excluent la possibilité
  624. d'implémenter un <a class="reference external" href="https://github.com/pouchdb/pouchdb/blob/master/lib/adapters/http/http.js#L721-L946">adapter PouchDB</a>
  625. pour la synchronisation avec le protocole HTTP de <em>Cliquet</em>.</p>
  626. <p>Dommage puisque capitaliser sur l'expérience client de <em>PouchDB</em> au niveau
  627. synchro client semble être une très bonne idée.</p>
  628. <p>En revanche, nous avons plusieurs fonctionnalités intéressantes:</p>
  629. <ul class="simple">
  630. <li>Pas de map-reduce;</li>
  631. <li>Synchronisation partielle et/ou ordonnée et/ou paginée ;</li>
  632. <li>Le client choisit, via des headers, d'écraser la donnée ou de respecter la version du serveur ;</li>
  633. <li>Un seul serveur à déployer pour N applications ;</li>
  634. <li>Auto-hébergement simplissime ;</li>
  635. <li>Le client peut choisir de ne pas utiliser de « store local » du tout ;</li>
  636. <li>Dans le client JS, la gestion du « store local » sera externalisée (on pense
  637. à <a class="reference external" href="https://github.com/mozilla/localForage">LocalForage</a> ou <a class="reference external" href="https://github.com/dfahlander/Dexie.js">Dexie.js</a>) ;</li>
  638. </ul>
  639. <p>Et, on répond au reste des <a class="reference external" href="storage-specs">specifications mentionnées au début de l'article</a> !</p>
  640. </div>
  641. <div class="section" id="les-arguments-philosophiques">
  642. <h3>Les arguments philosophiques</h3>
  643. <p>Il est <a class="reference external" href="http://en.wikipedia.org/wiki/Law_of_the_instrument">illusoire de penser qu'on peut tout faire avec un seul outil</a>.</p>
  644. <p>Nous avons d'autres cas d'utilisations dans les cartons qui semblent correspondre au scope de <em>PouchDB</em>
  645. (<em>pas de notion de permissions ou de partage, environnement JavaScript, ...</em>). Nous saurons en tirer
  646. profit quand cela s'avèrera pertinent !</p>
  647. <p>L'éco-système que nous voulons construire tentera de couvrir les cas d'utilisation
  648. qui sont mal adressés par <em>PouchDB</em>. Il se voudra:</p>
  649. <ul class="simple">
  650. <li>Basé sur notre protocole très simple ;</li>
  651. <li>Minimaliste et multi-usages (<em>comme la fameuse 2CV</em>) ;</li>
  652. <li>Naïf (<em>pas de rocket science</em>) ;</li>
  653. <li>Sans magie (<em>explicite et facile à réimplémenter from scratch</em>) ;</li>
  654. </ul>
  655. <p><a class="reference external" href="http://cliquet.readthedocs.org/en/latest/rationale.html">La philosophie et les fonctionnalités du toolkit python *Cliquet*</a> seront bien entendu
  656. à l'honneur :)</p>
  657. <p>Quant à <em>Remote Storage</em>, dès que le besoin se présentera, nous serons très fier
  658. de rejoindre l'initiative, mais pour l'instant cela nous paraît risqué de démarrer
  659. en tordant la solution.</p>
  660. </div>
  661. <div class="section" id="les-arguments-pratiques">
  662. <h3>Les arguments pratiques</h3>
  663. <p>Avant d'accepter de déployer une solution à base de <em>CouchDB</em>, les <em>ops</em> de Mozilla
  664. vont nous demander de leur prouver par A+B que ce n'est pas faisable avec
  665. les stacks qui sont déjà rodées en interne (i.e. MySQL, Redis, PostgreSQL).</p>
  666. <p>De plus, on doit s'engager sur une pérennité d'au moins 5 ans pour les données.
  667. Avec <em>Cliquet</em>, en utilisant le backend PostgreSQL, les données sont persistées
  668. à plat dans un <a class="reference external" href="https://github.com/mozilla-services/cliquet/blob/40aa33/cliquet/storage/postgresql/schema.sql#L14-L28">schéma PostgreSQL tout bête</a>.
  669. Ce qui ne sera pas le cas d'un adapteur LevelDown qui va manipuler des notions
  670. de révisions éclatées dans un schéma clé-valeur.</p>
  671. <p>Si nous basons le service sur <em>Cliquet</em>, tout le travail d'automatisation
  672. de la mise en production (<em>monitoring, builds RPM, Puppet...</em>) que nous avons
  673. fait pour <em>Reading List</em> est complètement réutilisable.</p>
  674. <p>De même, si on repart avec une stack complètement différente, nous allons
  675. devoir recommencer tout le travail de rodage, de profiling et d'optimisation
  676. effectué au premier trimestre.</p>
  677. </div>
  678. </div>
  679. <div class="section" id="les-prochaines-etapes">
  680. <h2>Les prochaines étapes</h2>
  681. <p>Et il est encore temps de changer de stratégie :) Nous aimerions avoir un
  682. maximum de retours ! C'est toujours une décision difficile à prendre...
  683. <tt class="docutils literal">&lt;/appel à troll&gt;</tt></p>
  684. <ul class="simple">
  685. <li>Tordre un éco-système existant vs. constuire sur mesure ;</li>
  686. <li>Maîtriser l'ensemble vs. s'intégrer ;</li>
  687. <li>Contribuer vs. refaire ;</li>
  688. <li>Guider vs. suivre.</li>
  689. </ul>
  690. <p>Nous avons vraiment l'intention de rejoindre l'initiative <a class="reference external" href="https://nobackend.org/">no-backend</a>, et ce premier pas n'exclue pas que nous convergions
  691. à terme ! Peut-être que nous allons finir par rendre notre service compatible
  692. avec <em>Remote Storage</em>, et peut-être que <em>PouchDB</em> deviendra plus agnostique
  693. quand au protocole de synchronisation...</p>
  694. <img alt="XKCD — Standards https://xkcd.com/927/" src="http://www.servicedenuages.fr/images/standards.png"/>
  695. <p>Utiliser ce nouvel écosystème pour le projet « Payments » va nous permettre de
  696. mettre au point un système de permissions (<em>basés sur les scopes OAuth</em>) qui
  697. correspond au besoin exprimé. Et nous avons bien l'intention de puiser dans
  698. <a class="reference external" href="http://blog.daybed.io/daybed-revival.html">notre expérience avec Daybed sur le sujet</a>.</p>
  699. <p>Nous extrairons aussi le code des clients implémentés pour <em>Reading List</em> afin
  700. de faire un client JavaScript minimaliste.</p>
  701. <p>En partant dans notre coin, nous prenons plusieurs risques:</p>
  702. <ul class="simple">
  703. <li>réinventer une roue dont nous n'avons pas connaissance;</li>
  704. <li>échouer à faire de l'éco-système <em>Cliquet</em> un projet communautaire;</li>
  705. <li>échouer à positionner <em>Cliquet</em> dans la niche des cas non couverts par PouchDB :)</li>
  706. </ul>
  707. <p>Comme <a class="reference external" href="http://pouchdb.com/2015/04/05/filtered-replication.html">le dit Giovanni Ornaghi</a>:</p>
  708. <blockquote>
  709. Rolling out your set of webservices, push notifications, or background services
  710. might give you more control, but at the same time it will force you to engineer,
  711. write, test, and maintain a whole new ecosystem.</blockquote>
  712. <p>C'est justement l'éco-système dont est responsable l'équipe <em>Mozilla Cloud Services</em>!</p>
  713. </article>
  714. </section>
  715. <nav id="jumpto">
  716. <p>
  717. <a href="/david/blog/">Accueil du blog</a> |
  718. <a href="http://www.servicedenuages.fr/eco-systeme-et-stockage-generique.html">Source originale</a> |
  719. <a href="/david/stream/2019/">Accueil du flux</a>
  720. </p>
  721. </nav>
  722. <footer>
  723. <div>
  724. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  725. <p>
  726. Bonjour/Hi!
  727. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  728. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  729. </p>
  730. <p>
  731. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  732. </p>
  733. <p>
  734. Voici quelques articles choisis :
  735. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  736. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  737. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  738. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  739. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  740. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  741. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  742. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  743. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  744. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  745. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  746. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  747. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  748. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  749. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  750. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  751. </p>
  752. <p>
  753. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  754. </p>
  755. <p>
  756. Je ne traque pas ta navigation mais mon
  757. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  758. conserve des logs d’accès.
  759. </p>
  760. </div>
  761. </footer>
  762. <script type="text/javascript">
  763. ;(_ => {
  764. const jumper = document.getElementById('jumper')
  765. jumper.addEventListener('click', e => {
  766. e.preventDefault()
  767. const anchor = e.target.getAttribute('href')
  768. const targetEl = document.getElementById(anchor.substring(1))
  769. targetEl.scrollIntoView({behavior: 'smooth'})
  770. })
  771. })()
  772. </script>