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

3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  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>Nameko for Microservices (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://lucumr.pocoo.org/2015/4/8/microservices-with-nameko/">
  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. Nameko for Microservices (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://lucumr.pocoo.org/2015/4/8/microservices-with-nameko/">Source originale du contenu</a></h3>
  445. <p>In December some of the tech guys at <a class="reference external" href="http://www.onefinestay.com/">onefinestay</a> invited me over to London to do some
  446. general improvements on their their <a class="reference external" href="http://nameko.readthedocs.org/en/latest/">nameko</a> library. This collaboration
  447. came together because nameko was pretty similar to how I generally like to
  448. build certain infrastructure and I had some experience with very similar
  449. systems.</p>
  450. <p>So now that some of those improvements hit the release version of nameko I
  451. figured it might be a good idea to give some feedback on why I like this
  452. sort of architecture.</p>
  453. <h2>Freeing your Mind</h2>
  454. <p>Right now if you want to build a web service in Python there are many
  455. tools you can pick from, but most of them live in a very specific part of
  456. your stack. The most common tool is a web framework and that will
  457. typically provide you with whatever glue is necessary to connect your own
  458. code to an incoming HTTP request that comes from your client.</p>
  459. <p>However that's not all you need in an application. For instance very
  460. often you have periodic tasks that you need to execute and in that case,
  461. your framework is often not just not helping, it's also in your way. For
  462. instance because you might have built your code with the assumption that
  463. it has access to the HTTP request object. If you now want to run it from
  464. a cronjob that request object is unavailable.</p>
  465. <p>In addition to crons there is often also the wish to execute something as
  466. the result of the request of a client, but without blocking that request.
  467. For instance imagine there is an admin panel in which you can trigger some
  468. very expensive data conversion task. What you actually want is for the
  469. current request to finish but the conversion task to keep on working in
  470. the background until your data set is converted.</p>
  471. <p>There are obviously many existing solutions for that. Celery comes to
  472. mind. However they are typically very separated from the rest of the
  473. stack.</p>
  474. <p>Having a system which treats all of this processes the same frees up your
  475. mind. This is what makes microservices interesting. Away with having
  476. HTTP request handlers that have no direct relationship with message queue
  477. worker tasks or cronjobs. Instead you can have a coherent system where
  478. any component can talk through well defined points with other parts of the
  479. system.</p>
  480. <p>This is especially useful in Python where traditionally our support for
  481. parallel execution has been between very bad to abysmal.</p>
  482. <h2>Enter Nameko</h2>
  483. <p>Nameko is an implementation of this idea. It's very similar in
  484. architecture to how we structure code at Fireteam. It's based on
  485. distributing work between processes through AMQP. It's not just AMQP
  486. though. Nameko abstracts away from that and allows you to write your own
  487. transports, while staying true to the AMQP patterns.</p>
  488. <p>Nameko does a handful of things and you can build very complex systems
  489. with it. The idea is that you build individual services which can emit
  490. events to which other services can subscribe to or they can directly
  491. invoke each other via RPC. All communication between the services happens
  492. through AMQP. You don't need to manually deal with any connectivity of
  493. those.</p>
  494. <p>In addition to message exchange, services also use a lifecycle management
  495. to find useful resources through dependency injection. That sounds like a
  496. mouthful but is actually very simple. Because services are classes, you
  497. can add special attributes to them which will be resolved at runtime. The
  498. lifetime of the value resolved can be customized. For instance it becomes
  499. possible to attach a property to the class which can provide access to a
  500. database connection. The lifetime of that database connection can be
  501. automatically managed.</p>
  502. <p>So how does that look in practice? Something like this:</p>
  503. <div class="highlight"><pre><span class="kn">from</span> <span class="nn">nameko.rpc</span> <span class="kn">import</span> <span class="n">rpc</span>
  504. <span class="k">class</span> <span class="nc">HelloWorldService</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
  505. <span class="n">name</span> <span class="o">=</span> <span class="s">&#39;helloworld&#39;</span>
  506. <span class="nd">@rpc</span>
  507. <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
  508. <span class="k">return</span> <span class="s">&quot;Hello, {}!&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
  509. </pre></div>
  510. <p>This defines a basic service that provides one method that can be invoked
  511. via RPC. Either another service can do that, or any other process that
  512. runs nameko can also invoke that, for as long as they connect to the same
  513. AMQP server. To experiment with this service, Nameko provides a shell
  514. helper that launches an interactive Python shell with an <tt class="docutils literal">n</tt> object that
  515. provides RPC access:</p>
  516. <div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">n</span><span class="o">.</span><span class="n">rpc</span><span class="o">.</span><span class="n">helloworld</span><span class="o">.</span><span class="n">hello</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">&#39;John&#39;</span><span class="p">)</span>
  517. <span class="go">u&#39;Hello, John!&#39;</span>
  518. </pre></div>
  519. <p>If the AMQP server is running, <tt class="docutils literal">rpc.helloworld.hello</tt> contacts the
  520. <tt class="docutils literal">helloworld</tt> service and resolves the <tt class="docutils literal">hello</tt> method on it. Upon
  521. calling this method a message will be dispatched via the AMQP broker and
  522. be picked up by a nameko process. The shell will then block and wait for
  523. the result to come back.</p>
  524. <p>A more useful example is what happens when services want to collaborate on
  525. some activity. For instance it's quite common that one service wants to
  526. respond to the changes another service performs to update it's own state.
  527. This can be achieved through events:</p>
  528. <div class="highlight"><pre><span class="kn">from</span> <span class="nn">nameko.events</span> <span class="kn">import</span> <span class="n">EventDispatcher</span><span class="p">,</span> <span class="n">event_handler</span>
  529. <span class="kn">from</span> <span class="nn">nameko.rpc</span> <span class="kn">import</span> <span class="n">rpc</span>
  530. <span class="k">class</span> <span class="nc">ServiceA</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
  531. <span class="n">name</span> <span class="o">=</span> <span class="s">&#39;servicea&#39;</span>
  532. <span class="n">dispatch</span> <span class="o">=</span> <span class="n">EventDispatcher</span><span class="p">()</span>
  533. <span class="nd">@rpc</span>
  534. <span class="k">def</span> <span class="nf">emit_an_event</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
  535. <span class="bp">self</span><span class="o">.</span><span class="n">dispatch</span><span class="p">(</span><span class="s">&#39;my_event_type&#39;</span><span class="p">,</span> <span class="s">&#39;payload&#39;</span><span class="p">)</span>
  536. <span class="k">class</span> <span class="nc">ServiceB</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
  537. <span class="n">name</span> <span class="o">=</span> <span class="s">&#39;serviceb&#39;</span>
  538. <span class="nd">@event_handler</span><span class="p">(</span><span class="s">&#39;servicea&#39;</span><span class="p">,</span> <span class="s">&#39;my_event_type&#39;</span><span class="p">)</span>
  539. <span class="k">def</span> <span class="nf">handle_an_event</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">payload</span><span class="p">):</span>
  540. <span class="k">print</span> <span class="s">&#39;service b received&#39;</span><span class="p">,</span> <span class="n">payload</span>
  541. </pre></div>
  542. <p>The default behavior is that one service instance of each service type
  543. will pick up the event. However nameko can also route an event to every
  544. single instance of every single service. This is useful for in-process
  545. cache invalidation for instance.</p>
  546. <h2>The Web</h2>
  547. <p>Nameko is not just good for internal communication however. It uses
  548. Werkzeug to provide a bridge to the outside world. This allows you to
  549. accept an HTTP request and to ingest a task into your service world:</p>
  550. <div class="highlight"><pre><span class="kn">import</span> <span class="nn">json</span>
  551. <span class="kn">from</span> <span class="nn">nameko.web.handlers</span> <span class="kn">import</span> <span class="n">http</span>
  552. <span class="kn">from</span> <span class="nn">werkzeug.wrappers</span> <span class="kn">import</span> <span class="n">Response</span>
  553. <span class="k">class</span> <span class="nc">HttpServiceService</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
  554. <span class="n">name</span> <span class="o">=</span> <span class="s">&#39;helloworld&#39;</span>
  555. <span class="nd">@http</span><span class="p">(</span><span class="s">&#39;GET&#39;</span><span class="p">,</span> <span class="s">&#39;/get/&lt;int:value&gt;&#39;</span><span class="p">)</span>
  556. <span class="k">def</span> <span class="nf">get_method</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
  557. <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s">&#39;value&#39;</span><span class="p">:</span> <span class="n">value</span><span class="p">}),</span>
  558. <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;application/json&#39;</span><span class="p">)</span>
  559. </pre></div>
  560. <p>The endpoint function can itself invoke other parts of the system via RPC
  561. or other methods.</p>
  562. <p>This functionality generally also extends into the websocket world, even
  563. though that part is currently quite experimental. It for instance is
  564. possible to listen to events and forward them into websocket connections.</p>
  565. <h2>Dependency Injection</h2>
  566. <p>One of the really neat design concepts in Nameko is the use of dependency
  567. injection to find resources. A good example is the SQLAlchemy bridge
  568. which attaches a SQLAlchemy database session to a service through
  569. dependency injection. The descriptor itself will hook into the lifecycle
  570. management to automatically manage the database resources:</p>
  571. <div class="highlight"><pre><span class="kn">from</span> <span class="nn">nameko_sqlalchemy</span> <span class="kn">import</span> <span class="n">Session</span>
  572. <span class="kn">import</span> <span class="nn">sqlalchemy</span> <span class="kn">as</span> <span class="nn">sa</span>
  573. <span class="kn">from</span> <span class="nn">sqlalchemy.ext.declarative</span> <span class="kn">import</span> <span class="n">declarative_base</span>
  574. <span class="n">Base</span> <span class="o">=</span> <span class="n">declarative_base</span><span class="p">()</span>
  575. <span class="k">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
  576. <span class="n">__tablename__</span> <span class="o">=</span> <span class="s">&#39;users&#39;</span>
  577. <span class="nb">id</span> <span class="o">=</span> <span class="n">sa</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">sa</span><span class="o">.</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  578. <span class="n">username</span> <span class="o">=</span> <span class="n">sa</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">sa</span><span class="o">.</span><span class="n">String</span><span class="p">)</span>
  579. <span class="k">class</span> <span class="nc">MyService</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
  580. <span class="n">name</span> <span class="o">=</span> <span class="s">&#39;myservice&#39;</span>
  581. <span class="n">session</span> <span class="o">=</span> <span class="n">Session</span><span class="p">(</span><span class="n">Base</span><span class="p">)</span>
  582. <span class="nd">@rpc</span>
  583. <span class="k">def</span> <span class="nf">get_username</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
  584. <span class="n">user</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">User</span><span class="p">)</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
  585. <span class="k">if</span> <span class="n">user</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
  586. <span class="k">return</span> <span class="n">user</span><span class="o">.</span><span class="n">username</span>
  587. </pre></div>
  588. <p>The implementation of the <tt class="docutils literal">Session</tt> dependency provider itself is
  589. ridiculously simple. The whole functionality could be implemented like
  590. this:</p>
  591. <div class="highlight"><pre><span class="kn">from</span> <span class="nn">weakref</span> <span class="kn">import</span> <span class="n">WeakKeyDictionary</span>
  592. <span class="kn">from</span> <span class="nn">nameko.extensions</span> <span class="kn">import</span> <span class="n">DependencyProvider</span>
  593. <span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
  594. <span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">sessionmaker</span>
  595. <span class="k">class</span> <span class="nc">Session</span><span class="p">(</span><span class="n">DependencyProvider</span><span class="p">):</span>
  596. <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">declarative_base</span><span class="p">):</span>
  597. <span class="bp">self</span><span class="o">.</span><span class="n">declarative_base</span> <span class="o">=</span> <span class="n">declarative_base</span>
  598. <span class="bp">self</span><span class="o">.</span><span class="n">sessions</span> <span class="o">=</span> <span class="n">WeakKeyDictionary</span><span class="p">()</span>
  599. <span class="k">def</span> <span class="nf">get_dependency</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">worker_ctx</span><span class="p">):</span>
  600. <span class="n">db_uri</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">container</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s">&#39;DATABASE_URL&#39;</span><span class="p">]</span>
  601. <span class="n">engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="n">db_uri</span><span class="p">)</span>
  602. <span class="n">session_cls</span> <span class="o">=</span> <span class="n">sessionmaker</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="n">engine</span><span class="p">)</span>
  603. <span class="bp">self</span><span class="o">.</span><span class="n">sessions</span><span class="p">[</span><span class="n">worker_ctx</span><span class="p">]</span> <span class="o">=</span> <span class="n">session</span> <span class="o">=</span> <span class="n">session_cls</span><span class="p">()</span>
  604. <span class="k">return</span> <span class="n">session</span>
  605. <span class="k">def</span> <span class="nf">worker_teardown</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">worker_ctx</span><span class="p">):</span>
  606. <span class="n">sess</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">sessions</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="n">worker_ctx</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
  607. <span class="k">if</span> <span class="n">sess</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
  608. <span class="n">sess</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
  609. </pre></div>
  610. <p>The actual implementation is only a tiny bit more complicated, and that is
  611. basically just a bit of extra code to support different database URLs for
  612. different services and declarative bases. Overall the concept is the same
  613. however. When the dependency is needed, a connection to the database is
  614. established and when the worker shuts down, the session is closed.</p>
  615. <h2>Concurrency and Parallelism</h2>
  616. <p>What makes nameko interesting is that scales out really well through the
  617. use of AMQP and eventlet. First of all, when nameko starts a service
  618. container it uses eventlet to patch up the Python interpreter to support
  619. green concurrency. This allows a service container to become quite
  620. concurrent to do multiple things at once. This is very useful when a
  621. service waits on another service as threads in Python are a very
  622. disappointing story. As this however largely eliminates the possibility
  623. of true parallelism it becomes necessary to start multiple instances of
  624. services to scale up. Thanks to the use of AMQP however, this becomes a
  625. very transparent process. For as long as services do not need to store
  626. local state, it becomes very trivial to run as many of those service
  627. containers as necessary.</p>
  628. <h2>My Take On It</h2>
  629. <p>Nameko as it stands has all the right principles for building a platform
  630. out of small services and it's probably the best Open Source solution for
  631. this problem in the Python world so far.</p>
  632. <p>It's a bit disappointing that Python's async story is so diverging between
  633. different Python versions and frameworks, but eventlet and gevent are by
  634. far the cleanest and most practical implementations, so for most intents
  635. and purposes the eventlet base in nameko is probably the best you can
  636. currently get for async IO. Fear not though, Nameko 2.0 now also runs on
  637. Python3.</p>
  638. <p>If you haven't tried this sort of service setup yet, you might want to
  639. give Nameko a try.</p>
  640. </article>
  641. </section>
  642. <nav id="jumpto">
  643. <p>
  644. <a href="/david/blog/">Accueil du blog</a> |
  645. <a href="http://lucumr.pocoo.org/2015/4/8/microservices-with-nameko/">Source originale</a> |
  646. <a href="/david/stream/2019/">Accueil du flux</a>
  647. </p>
  648. </nav>
  649. <footer>
  650. <div>
  651. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  652. <p>
  653. Bonjour/Hi!
  654. 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>
  655. 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>).
  656. </p>
  657. <p>
  658. 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>.
  659. </p>
  660. <p>
  661. Voici quelques articles choisis :
  662. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  663. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  664. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  665. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  666. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  667. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  668. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  669. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  670. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  671. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  672. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  673. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  674. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  675. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  676. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  677. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  678. </p>
  679. <p>
  680. 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>.
  681. </p>
  682. <p>
  683. Je ne traque pas ta navigation mais mon
  684. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  685. conserve des logs d’accès.
  686. </p>
  687. </div>
  688. </footer>
  689. <script type="text/javascript">
  690. ;(_ => {
  691. const jumper = document.getElementById('jumper')
  692. jumper.addEventListener('click', e => {
  693. e.preventDefault()
  694. const anchor = e.target.getAttribute('href')
  695. const targetEl = document.getElementById(anchor.substring(1))
  696. targetEl.scrollIntoView({behavior: 'smooth'})
  697. })
  698. })()
  699. </script>