graphiti 2.0.0.beta.2 → 2.0.0.beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -86
  3. data/.github/workflows/docs.yml +60 -0
  4. data/.github/workflows/release.yml +8 -8
  5. data/.gitignore +7 -0
  6. data/.npmrc +9 -0
  7. data/.standard.yml +4 -1
  8. data/Appraisals +33 -32
  9. data/CHANGELOG.md +41 -0
  10. data/README.md +13 -2
  11. data/UPGRADING.md +2 -68
  12. data/docs/concepts/backends-and-models.md +122 -0
  13. data/docs/concepts/endpoints.md +183 -0
  14. data/docs/concepts/links.md +212 -0
  15. data/docs/concepts/overview.md +80 -0
  16. data/docs/concepts/persisting.md +376 -0
  17. data/docs/concepts/relationships.md +527 -0
  18. data/docs/concepts/resources.md +677 -0
  19. data/docs/getting-started/first-api.md +289 -0
  20. data/docs/getting-started/installation.md +185 -0
  21. data/docs/intro.md +307 -0
  22. data/docs/js/authentication.md +63 -0
  23. data/docs/js/ddau.md +20 -0
  24. data/docs/js/extra-params.md +41 -0
  25. data/docs/js/index.md +112 -0
  26. data/docs/js/installation.md +120 -0
  27. data/docs/js/middleware.md +72 -0
  28. data/docs/js/models.md +202 -0
  29. data/docs/js/reads.md +494 -0
  30. data/docs/js/state-syncing.md +100 -0
  31. data/docs/js/writes.md +373 -0
  32. data/docs/reference/vandal.md +63 -0
  33. data/docs/reference/why.md +13 -0
  34. data/docs/topics/authorization.md +155 -0
  35. data/docs/topics/caching.md +55 -0
  36. data/docs/topics/customizing-sideloads.md +156 -0
  37. data/docs/topics/debugging.md +216 -0
  38. data/docs/topics/error-handling.md +210 -0
  39. data/docs/topics/etags.md +46 -0
  40. data/docs/topics/hopping-relationships.md +149 -0
  41. data/docs/topics/json-attributes.md +77 -0
  42. data/docs/topics/openstruct-models.md +50 -0
  43. data/docs/topics/remote-resources.md +291 -0
  44. data/docs/topics/testing.md +894 -0
  45. data/docs/topics/without-activerecord.md +324 -0
  46. data/docs/tutorial/index.md +58 -0
  47. data/docs/tutorial/step_0.md +107 -0
  48. data/docs/tutorial/step_1.md +199 -0
  49. data/docs/tutorial/step_2.md +312 -0
  50. data/docs/tutorial/step_3.md +142 -0
  51. data/docs/tutorial/step_4.md +135 -0
  52. data/docs/tutorial/step_5.md +69 -0
  53. data/docs/tutorial/step_6.md +82 -0
  54. data/docs/tutorial/step_7.md +205 -0
  55. data/docs/tutorial/step_8.md +128 -0
  56. data/docs/tutorial/step_9.md +171 -0
  57. data/docs/upgrading.md +265 -0
  58. data/gemfiles/rails_7_1.gemfile +4 -3
  59. data/gemfiles/{rails_7_2_graphiti_rails.gemfile → rails_7_2.gemfile} +3 -3
  60. data/gemfiles/{rails_8_1_graphiti_rails.gemfile → rails_8_0.gemfile} +3 -3
  61. data/gemfiles/{rails_8_0_graphiti_rails.gemfile → rails_8_1.gemfile} +3 -3
  62. data/graphiti.gemspec +7 -5
  63. data/{deprecated_generators → lib/generators}/graphiti/api_test_generator.rb +7 -1
  64. data/{deprecated_generators → lib/generators}/graphiti/generator_mixin.rb +14 -1
  65. data/{deprecated_generators → lib/generators}/graphiti/install_generator.rb +19 -13
  66. data/{deprecated_generators → lib/generators}/graphiti/resource_generator.rb +43 -6
  67. data/{deprecated_generators → lib/generators}/graphiti/templates/index_request_spec.rb.erb +1 -1
  68. data/{deprecated_generators → lib/generators}/graphiti/templates/resource_reads_spec.rb.erb +6 -6
  69. data/{deprecated_generators → lib/generators}/graphiti/templates/show_request_spec.rb.erb +1 -1
  70. data/lib/graphiti/configuration.rb +2 -2
  71. data/lib/graphiti/error_serializers/conflict_request.rb +19 -0
  72. data/lib/graphiti/error_serializers/deprecated_constants.rb +48 -0
  73. data/lib/graphiti/error_serializers/invalid_request.rb +56 -0
  74. data/lib/graphiti/error_serializers/validation.rb +143 -0
  75. data/lib/graphiti/errors.rb +4 -23
  76. data/lib/graphiti/query.rb +1 -1
  77. data/lib/graphiti/rails/context.rb +33 -0
  78. data/lib/graphiti/rails/controller.rb +41 -0
  79. data/lib/graphiti/rails/debugging.rb +18 -0
  80. data/lib/graphiti/rails/exception_handlers.rb +77 -0
  81. data/lib/graphiti/rails/railtie.rb +139 -0
  82. data/lib/graphiti/rails/responders.rb +21 -0
  83. data/lib/graphiti/rails/test_helpers.rb +22 -0
  84. data/lib/graphiti/rails.rb +47 -29
  85. data/lib/graphiti/resource/configuration.rb +1 -0
  86. data/lib/graphiti/resource/interface.rb +2 -2
  87. data/lib/graphiti/resource/persistence.rb +14 -2
  88. data/lib/graphiti/resource/remote.rb +2 -2
  89. data/lib/graphiti/resource/sideloading.rb +1 -1
  90. data/lib/graphiti/resource.rb +13 -1
  91. data/lib/graphiti/responders.rb +7 -20
  92. data/lib/graphiti/schema.rb +5 -1
  93. data/lib/graphiti/schema_diff.rb +4 -0
  94. data/lib/graphiti/scope.rb +45 -37
  95. data/lib/graphiti/serializer.rb +6 -0
  96. data/lib/graphiti/sideload/belongs_to.rb +38 -5
  97. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +27 -23
  98. data/lib/graphiti/sideload.rb +54 -35
  99. data/lib/graphiti/spec_helpers/errors.rb +73 -0
  100. data/lib/graphiti/spec_helpers/errors_proxy.rb +75 -0
  101. data/lib/graphiti/spec_helpers/helpers.rb +107 -0
  102. data/lib/graphiti/spec_helpers/node.rb +88 -0
  103. data/lib/graphiti/spec_helpers/rspec.rb +147 -0
  104. data/lib/graphiti/spec_helpers.rb +53 -0
  105. data/lib/graphiti/util/include_params.rb +2 -2
  106. data/lib/graphiti/util/persistence.rb +10 -11
  107. data/lib/graphiti/util/serializer_relationships.rb +41 -5
  108. data/lib/graphiti/version.rb +1 -1
  109. data/lib/graphiti-rails.rb +11 -0
  110. data/lib/graphiti.rb +34 -10
  111. data/lib/graphiti_errors.rb +11 -0
  112. data/lib/graphiti_spec_helpers/rspec.rb +3 -0
  113. data/lib/graphiti_spec_helpers.rb +11 -0
  114. data/lib/{graphiti/deprecated_tasks.rb → tasks/graphiti.rake} +6 -1
  115. data/package-lock.json +6199 -0
  116. data/package.json +5 -4
  117. data/website/.gitignore +20 -0
  118. data/website/README.md +43 -0
  119. data/website/docusaurus.config.js +141 -0
  120. data/website/package-lock.json +19474 -0
  121. data/website/package.json +46 -0
  122. data/website/sidebars.js +82 -0
  123. data/website/src/css/custom.css +58 -0
  124. data/website/src/pages/markdown-page.mdx +7 -0
  125. data/website/static/.nojekyll +0 -0
  126. data/website/static/1.13/2019/03/31/graphiti-1-0.html +205 -0
  127. data/website/static/1.13/2019/05/08/graphiti-1-1.html +212 -0
  128. data/website/static/1.13/2019/05/20/graphiti-1-2.html +214 -0
  129. data/website/static/1.13/2019/10/14/tutorial.html +198 -0
  130. data/website/static/1.13/CNAME +1 -0
  131. data/website/static/1.13/README.md +16 -0
  132. data/website/static/1.13/assets/css/syntax.css +60 -0
  133. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  134. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  135. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  136. data/website/static/1.13/assets/favicons/browserconfig.xml +9 -0
  137. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  138. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  139. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  140. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  141. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +1 -0
  142. data/website/static/1.13/assets/favicons/site.webmanifest +19 -0
  143. data/website/static/1.13/assets/img/backend.gif +0 -0
  144. data/website/static/1.13/assets/img/conformity.png +0 -0
  145. data/website/static/1.13/assets/img/error_payload.png +0 -0
  146. data/website/static/1.13/assets/img/gh.png +0 -0
  147. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  148. data/website/static/1.13/assets/img/logo-500.png +0 -0
  149. data/website/static/1.13/assets/img/logo.png +0 -0
  150. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  151. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  152. data/website/static/1.13/assets/img/persist.jpg +0 -0
  153. data/website/static/1.13/assets/img/resource.gif +0 -0
  154. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  155. data/website/static/1.13/assets/img/rest1.gif +0 -0
  156. data/website/static/1.13/assets/img/rest2.gif +0 -0
  157. data/website/static/1.13/assets/img/rest3.gif +0 -0
  158. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  159. data/website/static/1.13/assets/img/why.png +0 -0
  160. data/website/static/1.13/assets/js/highlight.pack.js +2 -0
  161. data/website/static/1.13/assets/main.css +15518 -0
  162. data/website/static/1.13/assets/main.css.map +1 -0
  163. data/website/static/1.13/bin/bundle +109 -0
  164. data/website/static/1.13/bin/jekyll +27 -0
  165. data/website/static/1.13/bin/kramdown +27 -0
  166. data/website/static/1.13/bin/listen +27 -0
  167. data/website/static/1.13/bin/rake +27 -0
  168. data/website/static/1.13/bin/rougify +27 -0
  169. data/website/static/1.13/bin/safe_yaml +27 -0
  170. data/website/static/1.13/bin/sass +27 -0
  171. data/website/static/1.13/bin/sass-convert +27 -0
  172. data/website/static/1.13/bin/scss +27 -0
  173. data/website/static/1.13/blog.html +259 -0
  174. data/website/static/1.13/cheatsheet.html +316 -0
  175. data/website/static/1.13/cookbooks/authorization.md +0 -0
  176. data/website/static/1.13/cookbooks/caching.md +0 -0
  177. data/website/static/1.13/cookbooks/customizing-sideloads.html +325 -0
  178. data/website/static/1.13/cookbooks/etags.md +0 -0
  179. data/website/static/1.13/cookbooks/hopping-relationships.html +324 -0
  180. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  181. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  182. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  183. data/website/static/1.13/cookbooks/without-activerecord.html +510 -0
  184. data/website/static/1.13/features.html +249 -0
  185. data/website/static/1.13/feed.xml +106 -0
  186. data/website/static/1.13/guides/concepts/backends-and-models.html +467 -0
  187. data/website/static/1.13/guides/concepts/debugging.html +440 -0
  188. data/website/static/1.13/guides/concepts/endpoints.html +432 -0
  189. data/website/static/1.13/guides/concepts/error-handling.html +396 -0
  190. data/website/static/1.13/guides/concepts/links.html +501 -0
  191. data/website/static/1.13/guides/concepts/remote-resources.html +536 -0
  192. data/website/static/1.13/guides/concepts/resources.html +2176 -0
  193. data/website/static/1.13/guides/concepts/testing.html +1469 -0
  194. data/website/static/1.13/guides/getting-started/installation.html +420 -0
  195. data/website/static/1.13/guides/graphiti-rails-migration.html +242 -0
  196. data/website/static/1.13/guides/index.html +269 -0
  197. data/website/static/1.13/guides/overview.html +325 -0
  198. data/website/static/1.13/guides/upgrading-2-0.html +193 -0
  199. data/website/static/1.13/guides/upgrading.html +314 -0
  200. data/website/static/1.13/guides/vandal.html +282 -0
  201. data/website/static/1.13/guides/why.html +1121 -0
  202. data/website/static/1.13/index.html +72 -0
  203. data/website/static/1.13/js/authentication.html +295 -0
  204. data/website/static/1.13/js/ddau.html +238 -0
  205. data/website/static/1.13/js/extra-params.html +270 -0
  206. data/website/static/1.13/js/index.html +321 -0
  207. data/website/static/1.13/js/installation.html +637 -0
  208. data/website/static/1.13/js/introduction.html +257 -0
  209. data/website/static/1.13/js/middleware.html +318 -0
  210. data/website/static/1.13/js/reads/fieldsets.html +271 -0
  211. data/website/static/1.13/js/reads/filtering.html +289 -0
  212. data/website/static/1.13/js/reads/includes.html +260 -0
  213. data/website/static/1.13/js/reads/index.html +497 -0
  214. data/website/static/1.13/js/reads/nested-queries.html +353 -0
  215. data/website/static/1.13/js/reads/pagination.html +260 -0
  216. data/website/static/1.13/js/reads/sorting.html +265 -0
  217. data/website/static/1.13/js/reads/statistics.html +289 -0
  218. data/website/static/1.13/js/state-syncing.html +340 -0
  219. data/website/static/1.13/js/writes/deferred.html +296 -0
  220. data/website/static/1.13/js/writes/dirty-tracking.html +399 -0
  221. data/website/static/1.13/js/writes/index.html +391 -0
  222. data/website/static/1.13/js/writes/nested.html +330 -0
  223. data/website/static/1.13/js/writes/validations.html +272 -0
  224. data/website/static/1.13/quickstart.html +660 -0
  225. data/website/static/1.13/template +161 -0
  226. data/website/static/1.13/tutorial/index.html +250 -0
  227. data/website/static/1.13/tutorial/step_0.html +292 -0
  228. data/website/static/1.13/tutorial/step_1.html +517 -0
  229. data/website/static/1.13/tutorial/step_2.html +481 -0
  230. data/website/static/1.13/tutorial/step_3.html +323 -0
  231. data/website/static/1.13/tutorial/step_4.html +318 -0
  232. data/website/static/1.13/tutorial/step_5.html +265 -0
  233. data/website/static/1.13/tutorial/step_6.html +276 -0
  234. data/website/static/1.13/tutorial/step_7.html +390 -0
  235. data/website/static/1.13/tutorial/step_8.html +316 -0
  236. data/website/static/1.13/tutorial/step_9.html +365 -0
  237. data/website/static/assets/img/error_payload.png +0 -0
  238. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  239. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  240. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  241. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  242. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  243. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  244. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  245. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  246. data/website/static/assets/img/meta_total_count.png +0 -0
  247. data/website/static/img/docusaurus-social-card.jpg +0 -0
  248. data/website/static/img/docusaurus.png +0 -0
  249. data/website/static/img/favicon.ico +0 -0
  250. data/website/static/img/logo.png +0 -0
  251. data/website/static/img/logo.svg +1 -0
  252. data/website/static/img/undraw_docusaurus_mountain.svg +171 -0
  253. data/website/static/img/undraw_docusaurus_react.svg +170 -0
  254. data/website/static/img/undraw_docusaurus_tree.svg +40 -0
  255. metadata +245 -46
  256. data/gemfiles/rails_6.gemfile +0 -18
  257. data/gemfiles/rails_6_graphiti_rails.gemfile +0 -19
  258. data/gemfiles/rails_7.gemfile +0 -18
  259. data/gemfiles/rails_7_1_graphiti_rails.gemfile +0 -19
  260. data/gemfiles/rails_7_graphiti_rails.gemfile +0 -19
  261. data/lib/graphiti/railtie.rb +0 -121
  262. /data/{deprecated_generators → lib/generators}/graphiti/resource_test_generator.rb +0 -0
  263. /data/{deprecated_generators → lib/generators}/graphiti/templates/application_resource.rb.erb +0 -0
  264. /data/{deprecated_generators → lib/generators}/graphiti/templates/controller.rb.erb +0 -0
  265. /data/{deprecated_generators → lib/generators}/graphiti/templates/create_request_spec.rb.erb +0 -0
  266. /data/{deprecated_generators → lib/generators}/graphiti/templates/destroy_request_spec.rb.erb +0 -0
  267. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource.rb.erb +0 -0
  268. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource_writes_spec.rb.erb +0 -0
  269. /data/{deprecated_generators → lib/generators}/graphiti/templates/update_request_spec.rb.erb +0 -0
@@ -0,0 +1,1121 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link href="https://fonts.googleapis.com/css?family=Roboto+Mono" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/css?family=Boogaloo" rel="stylesheet">
10
+
11
+ <link rel="apple-touch-icon" sizes="180x180" href="/1.13/assets/favicons/apple-touch-icon.png">
12
+ <link rel="icon" type="image/png" sizes="32x32" href="/1.13/assets/favicons/favicon-32x32.png">
13
+ <link rel="icon" type="image/png" sizes="16x16" href="/1.13/assets/favicons/favicon-16x16.png">
14
+ <link rel="manifest" href="/1.13/assets/favicons/site.webmanifest">
15
+ <link rel="mask-icon" href="/1.13/assets/favicons/safari-pinned-tab.svg" color="#F86DA7">
16
+ <link rel="shortcut icon" href="/1.13/assets/favicons/favicon.ico">
17
+ <meta name="msapplication-TileColor" content="#F86DA7">
18
+ <meta name="msapplication-config" content="/1.13/assets/favicons/browserconfig.xml">
19
+ <meta name="theme-color" content="#F86DA7">
20
+
21
+ <title>Graphiti</title>
22
+ <meta name="description" content="Stylish Graph APIs">
23
+
24
+ <link rel="stylesheet" href="/1.13/assets/main.css?ref=wh4t3v45">
25
+ <link rel="alternate" type="application/rss+xml" title="Graphiti" href="/1.13/feed.xml">
26
+
27
+ <!-- Global site tag (gtag.js) - Google Analytics -->
28
+ <script async src="https://www.googletagmanager.com/gtag/js?id=UA-127904727-1"></script>
29
+ <script>
30
+ window.dataLayer = window.dataLayer || [];
31
+ function gtag(){dataLayer.push(arguments);}
32
+ gtag('js', new Date());
33
+
34
+ gtag('config', 'UA-127904727-1');
35
+ </script>
36
+
37
+ <!-- javascript -->
38
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
39
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
40
+
41
+
42
+ </head>
43
+
44
+
45
+ <body>
46
+ <main class="page-content" aria-label="Content">
47
+ <div class="wrapper">
48
+ <header class="navbar navbar-inverse normal" role="banner">
49
+ <div class="container">
50
+ <div class="navbar-header">
51
+ <a href="/1.13/1.13" class="navbar-brand">
52
+ <img alt="logo" src="/1.13/assets/img/logo.png">
53
+ </a>
54
+ </div>
55
+ <nav class="" role="navigation">
56
+ <ul class="nav navbar-nav nav-links">
57
+ <li>
58
+ <a class="quickstart nav-link" href="/1.13/quickstart">Quickstart</a>
59
+ </li>
60
+ <li>
61
+ <a class="guides nav-link" href="/1.13/guides">Guides</a>
62
+ </li>
63
+ <li>
64
+ <a class="tutorial nav-link" href="/1.13/tutorial">Tutorial</a>
65
+ </li>
66
+ <li>
67
+ <a class="spraypaint nav-link" href="/1.13/js">Spraypaint</a>
68
+ </li>
69
+ </ul>
70
+ <ul class="nav gh navbar-nav navbar-right visible-lg visible-md">
71
+ <li>
72
+ <span class="star">⭐</span>
73
+ <a href="https://github.com/graphiti-api/graphiti">
74
+ <img alt="github-star" style="margin-right: 65px;margin-top: -20px" src="/1.13/assets/img/gh.png">
75
+ </a>
76
+ </li>
77
+ </ul>
78
+ </nav>
79
+ </div>
80
+ </header>
81
+
82
+ <div class="container">
83
+ <p align="center">
84
+ <img style="margin-top:-30px" width="100%" src="/1.13/assets/img/why.png" />
85
+ </p>
86
+
87
+ <h1 id="why-graphiti">Why Graphiti</h1>
88
+
89
+ <p>I’ve written GraphQL and had a pleasant experience. I have enormous respect for GraphQL. I also believe there is a fundamental flaw in GraphQL’s design.</p>
90
+
91
+ <p>Let’s remember <a href="https://dev.to/smizell/why-people-like-graphql-221c">why people like GraphQL</a> in the first place - because it addressed common frustrations with REST APIs:</p>
92
+
93
+ <blockquote>
94
+ <p><em>[We] heard from integrators that our REST API also wasn’t very flexible. It sometimes required two or three separate calls to assemble a complete view of a resource. It seemed like our responses simultaneously sent too much data and didn’t include data that consumers needed.</em></p>
95
+
96
+ <p>- <a href="https://githubengineering.com/the-github-graphql-api">“The Github Graph API”</a>, <strong>GitHub Engineering</strong></p>
97
+ </blockquote>
98
+
99
+ <p>GraphQL solves real problems. Its flaw is that it solved these
100
+ problems using zero-sum thinking: we must
101
+ abandon the existing paradigm and forge a new one. It’s GraphQL
102
+ versus REST, one or the other. <a href="https://medium.freecodecamp.org/rest-apis-are-rest-in-peace-apis-long-live-graphql-d412e559d8e4">REST is dead, long live GraphQL</a>.</p>
103
+
104
+ <p>Graphiti instead approaches the problem using <a href="http://aturon.github.io/tech/2018/06/02/listening-part-2/">positive-sum thinking</a>:</p>
105
+
106
+ <blockquote>
107
+ <p><em>Positive-sum thinking is how we embrace pluralism while retaining a coherent vision and set of values…A zero-sum view would assume that apparent oppositions are fundamental, e.g., that appealing to the JS crowd inherently hurts the C++ one. A positive-sum view starts by seeing different perspectives and priorities as legitimate and worthwhile, with a faith that</em> <strong>by respecting each other in this way, we can find strictly better solutions than had we optimized solely for one perspective.</strong></p>
108
+
109
+ <p>- <a href="http://aturon.github.io/tech/2018/06/02/listening-part-2">“Listening and Trust”</a>, <strong>Aaron Turon</strong></p>
110
+ </blockquote>
111
+
112
+ <p>GraphQL optimized around REST’s shortcomings, and in doing so it dropped
113
+ REST’s advantages. There is no need for such a zero-sum tradeoff. We can take
114
+ everything great about GraphQL and build it <strong><em>on top of</em></strong> REST (and
115
+ HTTP!), instead of replacing it altogether. We can have our cake and eat it too.</p>
116
+
117
+ <a class="anchor" id="rest" />
118
+ <a class="header" href="#rest">
119
+ <h2>
120
+ REST
121
+ </h2>
122
+ </a>
123
+
124
+ <p align="center">
125
+ <img width="100%" src="/1.13/assets/img/rest-graffiti.jpg" />
126
+ </p>
127
+ <div class="spacer"></div>
128
+
129
+ <p>You’ll find plenty of <a href="https://www.howtographql.com/basics/1-graphql-is-the-better-rest">GraphQL posts</a> that describe REST as an inflexible paradigm of a bygone era, a lack of granularity that necessarily leads to data under- and over-fetching.</p>
130
+
131
+ <p>It’s true that many REST APIs work this way, but this is not REST. While there’s
132
+ endless debate around which APIs are considered “RESTful”, I don’t think
133
+ we need to look much further than what the letters actually stand for:</p>
134
+
135
+ <div class="spacer"></div>
136
+
137
+ <blockquote>
138
+ <p><strong>Representational State Transfer</strong>. <em>This sentence is not only what REST stands for, it is also the tiniest possible description of what REST actually means…It is not a standard, rather a style describing the act of transfering a state of something by its representation.</em></p>
139
+
140
+ <p><em>Let’s consider this:</em></p>
141
+
142
+ <p><em>Marcus is a farmer. He has a ranch with 4 pigs, 12 chickens and 3 cows. He is now simulating a REST API while I am the client. If I want to request the current state of his farm using REST I just ask him: “State?”</em></p>
143
+
144
+ <p><em>Marcus answers: “4 pigs, 12 chickens, 3 cows”.
145
+ This is the most simple example of Representional State Transfer. Marcus transfered the state of his farm to me using a representation. The representation of the farm is the plain sentence: “4 pigs, 12 chickens, 3 cows”.</em></p>
146
+
147
+ <p><em>So lets get to the next level. How would I tell Marcus to add 2 cows to his farm the REST way?
148
+ Maybe tell him: “Marcus, please add 2 cows to your farm”.</em></p>
149
+
150
+ <p><em>Do you think this was REST? Are we transfering state by its representation here? <strong>NO!</strong> This was calling a remote procedure. The procedure of adding 2 cows to the farm.</em></p>
151
+
152
+ <p><em>Marcus sadly answers: “400, Bad Request. What do you mean?”</em></p>
153
+
154
+ <p><em>So lets try this again. How would we do this the REST way? What was the representation again? It was “4 pigs, 12 chickens, 3 cows”. Ok. so let’s try this again transfering the representation…</em></p>
155
+
156
+ <p><em>me: “Marcus, … 4 pigs, 12 chickens, 5 cows … please!”.</em>
157
+ <em>Marcus: “Alright !”.</em>
158
+ <em>me: “Marcus, … what is your state now?”.</em>
159
+ <em>Marcus: “4 pigs, 12 chickens, 5 cows”.</em>
160
+ <em>me: “Ahh, great!”</em>
161
+ <em>See? It was really not that hard and it was REST.</em></p>
162
+
163
+ <p>- <a href="http://www.beabetterdeveloper.com/2013/07/why-rest-is-so-important.html">“Why REST Is So Important”</a>, <strong>Gregor Riegler</strong></p>
164
+ </blockquote>
165
+
166
+ <p>In other words, <strong>this is REST</strong>:</p>
167
+
168
+ <div class="spacer"></div>
169
+ <p align="center">
170
+ <img width="80%" src="/1.13/assets/img/rest1.gif" />
171
+ </p>
172
+ <div class="spacer"></div>
173
+
174
+ <p>We’re moving an object from the server to the client, possibly modifying
175
+ that object, then moving back to the server.</p>
176
+
177
+ <p>The other important aspect of REST is <strong>Links</strong>. Because all objects are
178
+ addressable at a URL, we can use HTTP links to connect Resources. This
179
+ allows for (among other things) <strong>lazy-loading</strong> - the logic connecting Resources can be
180
+ changed server-side without breaking clients. If we have a “Top
181
+ Comments” relationship, we can redefine “Top” without disturbing
182
+ clients.</p>
183
+
184
+ <div class="spacer"></div>
185
+ <p align="center">
186
+ <img width="80%" src="/1.13/assets/img/rest2.gif" />
187
+ </p>
188
+ <div class="spacer"></div>
189
+
190
+ <p>Pretty simple right? Here’s how we might implement this in GraphQL:</p>
191
+
192
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">CreateEmployeeInput</span> <span class="p">{</span>
193
+ <span class="nl">name</span><span class="p">:</span> <span class="nb">String</span>
194
+ <span class="nx">age</span><span class="p">:</span> <span class="nx">Int</span>
195
+ <span class="p">}</span>
196
+
197
+ <span class="kd">type</span> <span class="nx">CreateEmployeePayload</span> <span class="p">{</span>
198
+ <span class="nl">employee</span><span class="p">:</span> <span class="nx">Employee</span>
199
+ <span class="p">}</span>
200
+
201
+ <span class="kd">type</span> <span class="nx">UpdateEmployeeInput</span> <span class="p">{</span>
202
+ <span class="nl">employeeId</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
203
+ <span class="nx">name</span><span class="p">:</span> <span class="nb">String</span>
204
+ <span class="nx">age</span><span class="p">:</span> <span class="nx">Int</span>
205
+ <span class="p">}</span>
206
+
207
+ <span class="kd">type</span> <span class="nx">UpdateEmployeePayload</span> <span class="p">{</span>
208
+ <span class="nl">employee</span><span class="p">:</span> <span class="nx">Employee</span>
209
+ <span class="p">}</span>
210
+
211
+ <span class="kd">type</span> <span class="nx">DestroyEmployeeInput</span> <span class="p">{</span>
212
+ <span class="nl">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
213
+ <span class="p">}</span>
214
+
215
+ <span class="kd">type</span> <span class="nx">DestroyEmployeePayload</span> <span class="p">{</span>
216
+ <span class="nl">employee</span><span class="p">:</span> <span class="nx">Employee</span>
217
+ <span class="p">}</span>
218
+
219
+ <span class="kd">type</span> <span class="nx">Employee</span> <span class="p">{</span>
220
+ <span class="nl">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
221
+ <span class="nx">name</span><span class="p">:</span> <span class="nb">String</span>
222
+ <span class="p">}</span>
223
+
224
+ <span class="nf">createEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">CreateEmployeeInput</span><span class="o">!</span><span class="p">):</span> <span class="nx">CreateEmployeePayload</span>
225
+ <span class="nf">updateEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">UpdateEmployeeInput</span><span class="o">!</span><span class="p">):</span> <span class="nx">UpdateEmployeePayload</span>
226
+ <span class="nf">destroyEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">DestroyEmployeeInput</span><span class="o">!</span><span class="p">):</span> <span class="nx">DestroyEmployeePayload</span>
227
+
228
+ <span class="nf">employee</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span><span class="p">):</span> <span class="nx">Employee</span></code></pre></figure>
229
+
230
+ <p>The first thing to note is that GraphQL is super badass at describing
231
+ fields and types. The next thing to note is that fields and types are
232
+ the wrong abstraction.</p>
233
+
234
+ <p>Defining a schema like this allows bespoke, fine-grained detail. If we
235
+ wanted, the <code class="language-plaintext highlighter-rouge">CreateEmployeePayload</code> could be different than the
236
+ <code class="language-plaintext highlighter-rouge">UpdateEmployeePayload</code> - same for inputs like <code class="language-plaintext highlighter-rouge">CreateEmployeeInput</code> and
237
+ <code class="language-plaintext highlighter-rouge">UpdateEmployeeInput</code>. If we wanted other actions, like
238
+ <code class="language-plaintext highlighter-rouge">promoteEmployee</code> or <code class="language-plaintext highlighter-rouge">deactivateEmployee</code>, they would be easy to add and
239
+ follow the same basic constructs.</p>
240
+
241
+ <p>This is <strong>RPC</strong> - hand-crafted, custom requests. We have a high level of
242
+ <strong>configuration</strong> but a low level of <strong>convention</strong>. Not only will
243
+ developers have to spend more time hand-crafting these requests, but
244
+ patterns are likely to diverge from one API to the next, from team to
245
+ team, as time moves on. In fact, the above is really a best-case
246
+ scenario with common naming convention of <code class="language-plaintext highlighter-rouge">create/update/destroy</code> - the
247
+ Github API adds verbs like <code class="language-plaintext highlighter-rouge">add</code>, <code class="language-plaintext highlighter-rouge">remove</code>, <code class="language-plaintext highlighter-rouge">lock</code>, <code class="language-plaintext highlighter-rouge">move</code> and more.</p>
248
+
249
+ <p>Oh, and we dropped support for lazy-loading along the way.</p>
250
+
251
+ <a class="anchor" id="conventions" />
252
+ <a class="header" href="#conventions">
253
+ <h2>
254
+ Conventions
255
+ </h2>
256
+ </a>
257
+
258
+ <p align="center">
259
+ <img src="/1.13/assets/img/conformity.png" />
260
+ </p>
261
+
262
+ <p>The benefit of REST over RPC is conventions. Conventions cause increased
263
+ productivity and consistency (leading to fewer misunderstandiings and
264
+ chances for bugs). Let’s start thinking in REST, and see where it takes
265
+ us.</p>
266
+
267
+ <p>In REST, we know the input and output is always the Resource:</p>
268
+
269
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">Employee</span> <span class="p">{</span>
270
+ <span class="nl">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
271
+ <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
272
+ <span class="p">}</span>
273
+
274
+ <span class="nf">createEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">Employee</span><span class="p">):</span> <span class="nx">Employee</span>
275
+ <span class="nf">updateEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">Employee</span><span class="o">!</span><span class="p">):</span> <span class="nx">Employee</span>
276
+ <span class="nf">destroyEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">Employee</span><span class="o">!</span><span class="p">):</span> <span class="nx">Employee</span>
277
+
278
+ <span class="nf">employee</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="nx">ID</span><span class="p">):</span> <span class="nx">Employee</span></code></pre></figure>
279
+
280
+ <p>OK, a little tighter. But there’s actually no reason to type this out
281
+ each time - we can assume developers are already familiar with the
282
+ convention.</p>
283
+
284
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">Employee</span> <span class="p">{</span>
285
+ <span class="nl">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
286
+ <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
287
+ <span class="p">}</span>
288
+
289
+ <span class="nx">createEmployee</span>
290
+ <span class="nx">updateEmployee</span>
291
+ <span class="nx">destroyEmployee</span>
292
+
293
+ <span class="nf">employee</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="nx">ID</span><span class="p">)</span></code></pre></figure>
294
+
295
+ <p>Getting there. OK, and we know we’re dealing with an Employee, and we
296
+ know we won’t have custom verbs like <code class="language-plaintext highlighter-rouge">promote</code> or <code class="language-plaintext highlighter-rouge">remove</code> - we’re
297
+ moving objects here, and nothing else (if that throws you for a mental
298
+ loop, see <a href="https://www.youtube.com/watch?v=HctYHe-YjnE">this presentation by Derek Prior</a>, Engineering Manager at
299
+ GitHub).</p>
300
+
301
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">Employee</span> <span class="p">{</span>
302
+ <span class="nl">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
303
+ <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
304
+ <span class="p">}</span>
305
+
306
+ <span class="nx">create</span>
307
+ <span class="nx">update</span>
308
+ <span class="nx">destroy</span>
309
+ <span class="nx">show</span> <span class="c1">// employee(id: ID)</span>
310
+ <span class="nx">index</span> <span class="c1">// employee()</span></code></pre></figure>
311
+
312
+ <p>By adopting conventions, we not only removed boilerplate - we removed
313
+ the chance of subtle inconsistencies. This is better for both providers
314
+ and consumers of the API.</p>
315
+
316
+ <p>We’re just getting started.</p>
317
+
318
+ <p>The above schema covers basic CRUD. But we probably want to filter data, right? Let’s say we want to return all employees with a given name:</p>
319
+
320
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">employee</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="nx">ID</span><span class="p">,</span> <span class="nx">name</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span></code></pre></figure>
321
+
322
+ <p>Again, we’re seeing chances for inconsistency. What’s the <code class="language-plaintext highlighter-rouge">name</code>
323
+ parameter - straight equality? Case sensitive? Contains? I guess we
324
+ could throw a bunch of suffixes at it:</p>
325
+
326
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">employee</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="nx">ID</span><span class="p">,</span> <span class="nx">name_eq</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="nx">name_suffix</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="nx">name_prefix</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="nx">name_contains</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="nx">name_not_eq</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="nx">name_not_suffix</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="nx">name_prefix</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="nx">name_not_prefix</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span></code></pre></figure>
327
+
328
+ <p>Works, but a bit unwieldy…and again, likely to diverge wildly across
329
+ implementations.</p>
330
+
331
+ <p>What about sorting? Should we do it similar to the <a href="https://developer.github.com/v4/explorer/">Github API</a>?:</p>
332
+
333
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kr">enum</span> <span class="nx">OrderDirection</span> <span class="p">{</span>
334
+ <span class="nx">ASC</span>
335
+ <span class="nx">DESC</span>
336
+ <span class="p">}</span>
337
+
338
+ <span class="kr">enum</span> <span class="nx">EmployeeOrderField</span> <span class="p">{</span>
339
+ <span class="nx">ID</span>
340
+ <span class="nx">NAME</span>
341
+ <span class="p">}</span>
342
+
343
+ <span class="kd">type</span> <span class="nx">EmployeeOrder</span> <span class="p">{</span>
344
+ <span class="nl">field</span><span class="p">:</span> <span class="nx">EmployeeOrderField</span><span class="o">!</span>
345
+ <span class="nx">direction</span><span class="p">:</span> <span class="nx">OrderDirection</span>
346
+ <span class="p">}</span>
347
+
348
+ <span class="nf">employee</span><span class="p">(</span><span class="nx">orderBy</span><span class="p">:</span> <span class="nx">EmployeeOrder</span><span class="p">)</span></code></pre></figure>
349
+
350
+ <p>Or should we do it like <a href="https://www.howtographql.com/graphql-js/8-filtering-pagination-and-sorting/">How to
351
+ GraphQL</a>?:</p>
352
+
353
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kr">enum</span> <span class="nx">EmployeeOrderByInput</span> <span class="p">{</span>
354
+ <span class="nx">id_ASC</span>
355
+ <span class="nx">id_DESC</span>
356
+ <span class="nx">name_ASC</span>
357
+ <span class="nx">name_DESC</span>
358
+ <span class="p">}</span>
359
+
360
+ <span class="nf">employee</span><span class="p">(</span><span class="nx">orderBy</span><span class="p">:</span> <span class="nx">EmployeeOrderByInput</span><span class="p">)</span></code></pre></figure>
361
+
362
+ <p>We have divergent APIs right off the bat, and neither one supports
363
+ multisort.</p>
364
+
365
+ <h2 id="rethinking-rest">Rethinking REST</h2>
366
+
367
+ <p align="center">
368
+ <img width="100%" src="/1.13/assets/img/rethink-rest-graffiti.jpg" />
369
+ </p>
370
+ <div class="spacer"></div>
371
+
372
+ <p>Earlier, we covered RESTfully moving an object between the server and
373
+ the client. Those objects connected together with Links, which allowed
374
+ for lazy-loading.</p>
375
+
376
+ <p>Instead of throwing away this paradigm, what if we just <strong>added
377
+ eager-loading</strong>?</p>
378
+
379
+ <div class="spacer"></div>
380
+ <p align="center">
381
+ <img width="80%" src="/1.13/assets/img/rest3.gif" />
382
+ </p>
383
+ <div class="spacer"></div>
384
+
385
+ <p>REST doesn’t have a query
386
+ specification or built-in schema, but it does have this Resource concept. <strong>Instead of
387
+ thinking of a bag of fields and types, what if we thought in Resources</strong>?:</p>
388
+
389
+ <p>Resources have <strong>Attributes</strong>. An <code class="language-plaintext highlighter-rouge">Employee</code> has a <code class="language-plaintext highlighter-rouge">first_name</code> which is
390
+ a <code class="language-plaintext highlighter-rouge">string</code>, an <code class="language-plaintext highlighter-rouge">age</code> which is an <code class="language-plaintext highlighter-rouge">integer</code>, and so forth.</p>
391
+
392
+ <p>We’d probably want to filter and sort by these attributes right? We might add
393
+ some additional filters and sorts, we might want to opt-out of others,
394
+ but querying a Resource by its attributes serves as a reasonable
395
+ baseline.</p>
396
+
397
+ <p>If we have an attribute and it’s a <code class="language-plaintext highlighter-rouge">string</code>, we know we’re
398
+ talking about operators like <code class="language-plaintext highlighter-rouge">suffix</code> and <code class="language-plaintext highlighter-rouge">prefix</code>, but an <code class="language-plaintext highlighter-rouge">integer</code>
399
+ attribute would want operators like <code class="language-plaintext highlighter-rouge">greater_than</code> and <code class="language-plaintext highlighter-rouge">less_than</code>.</p>
400
+
401
+ <p>Resources also have <strong>relationships</strong> to other Resources. We should be
402
+ able to lazy-load those relationships (with <strong>Links</strong>), or eager-load
403
+ those relationships. Whether lazy or eager, the same logic should apply.</p>
404
+
405
+ <p>If we defined these Resources, with their attributes and relationships,
406
+ then <strong>the input and the output wouldn’t actually matter</strong>. Or more
407
+ accurately: we can <em><strong>accept</strong> and <strong>render</strong> whatever payload we want</em>. Maybe
408
+ we’ll even dynamically serve different payloads based on <code class="language-plaintext highlighter-rouge">Content-Type</code>.</p>
409
+
410
+ <p>OK, so really we don’t need to define <em>inputs</em> and <em>outputs</em> - those can
411
+ be assumed by convention, swapped on-demand. What we really need to define is the <strong>Resource</strong>.</p>
412
+
413
+ <a class="anchor" id="welcome-to-graphiti" />
414
+ <a class="header" href="#welcome-to-graphiti">
415
+ <h2>
416
+ Welcome to Graphiti
417
+ </h2>
418
+ </a>
419
+
420
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">EmployeeResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
421
+ <span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">sortable: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">filterable: </span><span class="kp">true</span>
422
+ <span class="n">attribute</span> <span class="ss">:age</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">writable: </span><span class="kp">false</span>
423
+ <span class="k">end</span></code></pre></figure>
424
+
425
+ <p>With nothing but this Resource definition and some assumed conventions,
426
+ we get all this behavior out of the box:</p>
427
+
428
+ <ul>
429
+ <li>Create</li>
430
+ <li>Update</li>
431
+ <li>Delete</li>
432
+ <li>Read
433
+ <ul>
434
+ <li>Filter
435
+ <ul>
436
+ <li>String (<code class="language-plaintext highlighter-rouge">name</code>)
437
+ <ul>
438
+ <li><code class="language-plaintext highlighter-rouge">eq</code> (case sensitive)</li>
439
+ <li><code class="language-plaintext highlighter-rouge">eql</code> (case insensitive)</li>
440
+ <li><code class="language-plaintext highlighter-rouge">prefix</code></li>
441
+ <li><code class="language-plaintext highlighter-rouge">suffix</code></li>
442
+ <li><code class="language-plaintext highlighter-rouge">match</code></li>
443
+ <li><code class="language-plaintext highlighter-rouge">not_*</code> (<code class="language-plaintext highlighter-rouge">not_eq</code>, <code class="language-plaintext highlighter-rouge">not_prefix</code>, etc)</li>
444
+ </ul>
445
+ </li>
446
+ <li>Dates and Numbers (<code class="language-plaintext highlighter-rouge">age</code>)
447
+ <ul>
448
+ <li><code class="language-plaintext highlighter-rouge">eq</code></li>
449
+ <li><code class="language-plaintext highlighter-rouge">gt</code> (greater than)</li>
450
+ <li><code class="language-plaintext highlighter-rouge">lt</code> (less than)</li>
451
+ <li><code class="language-plaintext highlighter-rouge">gte</code> (greater than/equal to)</li>
452
+ <li><code class="language-plaintext highlighter-rouge">lte</code> (less than/equal to)</li>
453
+ </ul>
454
+ </li>
455
+ </ul>
456
+ </li>
457
+ <li>Sort / Multisort</li>
458
+ <li>Paginate</li>
459
+ <li>Fieldsets</li>
460
+ </ul>
461
+ </li>
462
+ </ul>
463
+
464
+ <p>Not just an API contract, but <strong>out-of-the-📦 behavior</strong>. If we know
465
+ the configuration of a Resource, we can <strong>automate query and persistence
466
+ operations</strong>. Obviously we’ll need lots of ways to customize and
467
+ override, and support for any number of datastores and clients. But if
468
+ we have the configuration, a defined contract for querying and
469
+ persisting data, we can build patterns around it.</p>
470
+
471
+ <p>There’s more to this than a bunch of out-of-the-📦 standards and
472
+ behavior. If we thought only in Fields and Types, we’d use GraphiQL to
473
+ see something like:</p>
474
+
475
+ <p><br /></p>
476
+
477
+ <p align="center">
478
+ <img width="30%" class="drop" src="https://user-images.githubusercontent.com/55264/52915902-78418d80-32a7-11e9-8515-021312258400.png" />
479
+ </p>
480
+
481
+ <p><br /></p>
482
+
483
+ <p>But if we thought in Resources, it’s not just a big bag of fields. We’re
484
+ able to organize those fields around meaningful concepts:</p>
485
+
486
+ <p><br /></p>
487
+
488
+ <p align="center">
489
+ <img width="40%" class="drop" src="https://user-images.githubusercontent.com/55264/52916024-e3d82a80-32a8-11e9-8bb6-07ac9bf988dc.png" />
490
+ </p>
491
+
492
+ <p><br /></p>
493
+
494
+ <p>This screenshot is from <a href="/1.13/guides/vandal">Vandal</a>, the Graphiti UI.</p>
495
+
496
+ <p>Because we started with a better abstraction, we ended with a more
497
+ intuitive
498
+ UI. As a marketer-turned-programmer myself, I really
499
+ appreciate when data exploration tools like this are friendly to
500
+ less-technical users. A user of Vandal doesn’t need to know about <code class="language-plaintext highlighter-rouge">Connection</code>s
501
+ or <code class="language-plaintext highlighter-rouge">Edge</code>s, they just need to click around. I like that my product owner and I can walk
502
+ through the domain together, validating concepts and solidifying a shared
503
+ understanding.</p>
504
+
505
+ <a class="anchor" id="http-endpoints" />
506
+ <a class="header" href="#http-endpoints">
507
+ <h4>
508
+ HTTP Endpoints
509
+ </h4>
510
+ </a>
511
+
512
+ <p>While both GraphQL and Graphiti can fetch your graph of data in a single
513
+ request, GraphQL does this with a single endpoint. Graphiti keeps
514
+ RESTful URLs, for three reasons. First, HTTP caching: <a href="https://philsturgeon.uk/api/2017/01/26/graphql-vs-rest-caching/">Phil Sturgeon has
515
+ an excellent comparison of GraphQL and endpoint-based caching here</a>.</p>
516
+
517
+ <p>Second, adding endpoints allows for customization. Maybe we want to send
518
+ a welcome email when we <code class="language-plaintext highlighter-rouge">POST /employees</code> but not when we <code class="language-plaintext highlighter-rouge">POST
519
+ /admin/employees</code>. Maybe <code class="language-plaintext highlighter-rouge">/exemplary_employees</code> applies additional query
520
+ logic by default, but wants to re-use everything else. In other words,
521
+ Resources form your graph, but Endpoints <em>expose</em> that graph to the
522
+ outside world (and hold relevant logic around exposure). Read more in
523
+ our <a href="/1.13/guides/concepts/endpoints">Endpoints Guide</a>.</p>
524
+
525
+ <p>The third reason is lazy-loading with <a href="/1.13/graphiti/guides/concepts/links">Links</a>, which we’ll cover in a bit.</p>
526
+
527
+ <a class="anchor" id="schema" />
528
+ <a class="header" href="#schema">
529
+ <h4>
530
+ Schema
531
+ </h4>
532
+ </a>
533
+
534
+ <p>We even get schema benefits. Schemas are great for tooling and
535
+ backwards-compatibility checks…but when they are oriented around
536
+ Fields and Types, they can only tell you so much. When they are oriented
537
+ around Resources, they can expose less-obvious concepts. Maybe we sort
538
+ Employees by <code class="language-plaintext highlighter-rouge">created_at</code> by default:</p>
539
+
540
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
541
+ <span class="ss">name: </span><span class="s2">"EmployeeResource"</span><span class="p">,</span>
542
+ <span class="ss">type: </span><span class="s2">"employees"</span><span class="p">,</span>
543
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="o">...</span> <span class="p">},</span>
544
+ <span class="ss">default_sort: </span><span class="p">[{</span> <span class="s2">"created_at"</span><span class="p">:</span> <span class="s2">"desc"</span> <span class="p">}],</span>
545
+ <span class="o">...</span>
546
+ <span class="p">}</span></code></pre></figure>
547
+
548
+ <p>Because this is specified in the schema, not only are clients more
549
+ informed, but changing this default would raise a backwards-compatibility
550
+ error:</p>
551
+
552
+ <figure class="highlight"><pre><code class="language-error" data-lang="error">EmployeeResource: default sort changed from [{:created_at=&gt;"desc"}] to [{:last_name=&gt;"asc"}].</code></pre></figure>
553
+
554
+ <p>When developing in Graphiti, we introspect your Resources and
555
+ automatically generate the schema for you. Backwards-compatibility
556
+ checks can be done with a command-line task, or whenever your tests run.</p>
557
+
558
+ <a class="anchor" id="graphs" />
559
+ <a class="header" href="#graphs">
560
+ <h2>
561
+ Graphs
562
+ </h2>
563
+ </a>
564
+
565
+ <p>You may be thinking, “<em>OK, but REST only works for a single object. I’ll
566
+ have to make multiple requests, and be right back where I started. I
567
+ need GraphQL to solve this problem</em>”.</p>
568
+
569
+ <p>Not true.</p>
570
+
571
+ <p>Years before GraphQL came out, respected developers from different
572
+ companies and backgrounds came together and began the discussion on how to improve REST
573
+ APIs. This wasn’t a project pushed by a hundred-billion dollar
574
+ company; it was an organic, community-driven effort. The result was the <a href="https://jsonapi.org">JSON:API</a> standard,
575
+ which tackled granular queries long ago:</p>
576
+
577
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># GET /employees?include=positions</span>
578
+
579
+ <span class="p">{</span>
580
+ <span class="ss">data: </span><span class="p">{</span>
581
+ <span class="ss">id: </span><span class="s2">"123"</span><span class="p">,</span>
582
+ <span class="ss">type: </span><span class="s2">"employees"</span><span class="p">,</span>
583
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">name: </span><span class="s2">"Kayla Webb"</span> <span class="p">},</span>
584
+ <span class="ss">relationships: </span><span class="p">{</span>
585
+ <span class="ss">positions: </span><span class="p">{</span>
586
+ <span class="ss">data: </span><span class="p">{</span> <span class="ss">id: </span><span class="s2">"456"</span><span class="p">,</span> <span class="ss">type: </span><span class="s2">"positions"</span> <span class="p">}</span>
587
+ <span class="p">}</span>
588
+ <span class="p">}</span>
589
+ <span class="p">},</span>
590
+ <span class="ss">included: </span><span class="p">[</span>
591
+ <span class="p">{</span>
592
+ <span class="ss">id: </span><span class="s2">"456"</span><span class="p">,</span>
593
+ <span class="ss">type: </span><span class="s2">"positions"</span><span class="p">,</span>
594
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">title: </span><span class="s2">"Engineer"</span> <span class="p">}</span>
595
+ <span class="p">}</span>
596
+ <span class="p">]</span>
597
+ <span class="p">}</span></code></pre></figure>
598
+
599
+ <p>Wonky payload, right? First of all, don’t be scared. In Graphiti,
600
+ you can add <code class="language-plaintext highlighter-rouge">.json</code> to the URL and output a more traditional flat
601
+ structure:</p>
602
+
603
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
604
+ <span class="ss">data: </span><span class="p">{</span>
605
+ <span class="ss">id: </span><span class="s2">"123"</span><span class="p">,</span>
606
+ <span class="ss">name: </span><span class="s2">"Jeesoo Ryoo"</span><span class="p">,</span>
607
+ <span class="ss">positions: </span><span class="p">[{</span> <span class="ss">id: </span><span class="s2">"456"</span><span class="p">,</span> <span class="ss">title: </span><span class="s2">"Engineer"</span> <span class="p">}]</span>
608
+ <span class="p">}</span>
609
+ <span class="p">}</span></code></pre></figure>
610
+
611
+ <p>You can absolutely develop in Graphiti this way, but you’d be giving up
612
+ some smart things JSON:API does. One example is de-duplicating each node
613
+ in the graph: if we’re listing 100 <code class="language-plaintext highlighter-rouge">Post</code>s and they all have the same
614
+ <code class="language-plaintext highlighter-rouge">Author</code>, you’ll have to render that <code class="language-plaintext highlighter-rouge">Author</code> 100 times. JSON:API would
615
+ only render it once.</p>
616
+
617
+ <p>Another is the <code class="language-plaintext highlighter-rouge">type/id</code> combo:</p>
618
+
619
+ <p align="center">
620
+ <div style="width: 500px;margin:auto">
621
+ <blockquote class="twitter-tweet" data-theme="dark" data-lang="en"><p lang="en" dir="ltr">Namely, those constraints are that all entities must be addressable top level by type and ID. (Very similar to JSON:API in this respect.)</p>&mdash; Tom Dale (@tomdale) <a href="https://twitter.com/tomdale/status/786951015945895936?ref_src=twsrc%5Etfw">October 14, 2016</a></blockquote>
622
+ <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
623
+ </div>
624
+ </p>
625
+
626
+ <p>There’s a bunch of reasons this is important, but one I’ve always been
627
+ partial to is websockets. This <code class="language-plaintext highlighter-rouge">type/id</code> combo allows us to uniquely
628
+ identify records once they’ve been loaded in JS memory. So, every time a
629
+ Resource is saved we can push its state (JSON representation) to clients
630
+ with a websocket. Here I am randomly updating a bunch of backend data,
631
+ and watching the UI update in real-time.</p>
632
+
633
+ <p align="center">
634
+ <img width="80%" src="https://user-images.githubusercontent.com/55264/38929932-042408da-42dc-11e8-93ec-16b0f9b62da3.gif" />
635
+ </p>
636
+ <p><br /></p>
637
+
638
+ <p>This took only a handful of lines of code, all of which could be
639
+ packaged into a library to make this automatic.</p>
640
+
641
+ <p>Another example is lazy-loading data with Links:</p>
642
+
643
+ <p align="center">
644
+ <div style="width: 500px;margin:auto">
645
+ <blockquote class="twitter-tweet" data-theme="dark" data-conversation="none" data-lang="en"><p lang="en" dir="ltr">A GraphQL response is going to be as slow as the slowest subquery it has to execute to build the response.</p>&mdash; Tom Dale (@tomdale) <a href="https://twitter.com/tomdale/status/786952448799825921?ref_src=twsrc%5Etfw">October 14, 2016</a></blockquote>
646
+ <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
647
+ </div>
648
+ </p>
649
+
650
+ <p>Instead of loading everything up-front, we want to defer loading for
651
+ performance reasons. Maybe we want to render our <code class="language-plaintext highlighter-rouge">Employee</code> detail page
652
+ super quick, and we don’t need to list the <code class="language-plaintext highlighter-rouge">Position</code>s until the user
653
+ clicks something.</p>
654
+
655
+ <p>You can do this in GraphQL, but you need to bake logic into the client,
656
+ which means changing the logic would break clients (read more about
657
+ this in the <a href="/1.13/guides/concepts/links">Links Guide</a>).
658
+ Luckily, REST and JSON:API are optimized for lazy-loading:</p>
659
+
660
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># ...</span>
661
+ <span class="ss">positions: </span><span class="p">{</span>
662
+ <span class="ss">links: </span><span class="p">{</span>
663
+ <span class="ss">related: </span><span class="s1">'http://example.com/api/positions?filter[employee_id]=123'</span>
664
+ <span class="p">}</span>
665
+ <span class="p">}</span>
666
+ <span class="c1"># ...</span></code></pre></figure>
667
+
668
+ <p>Graphiti generates these Links between Resources automatically. If you
669
+ change the logic connecting Resources (which applies to eager loading as
670
+ well), we’ll update the <a href="/1.13/guides/concepts/links">Link</a> - clients can simply follow the
671
+ link, and we can change logic server-side with no breakage.</p>
672
+
673
+ <p>There’s a bunch of other great stuff about JSON:API, but that is a
674
+ topic for another day.</p>
675
+
676
+ <blockquote>
677
+ <p>(<em>Quick aside: I’ve always found the name of this project hilariously
678
+ awkward, easy to confuse with any API outputting JSON. But you could say
679
+ the same about GraphQL! Just as JSON:API isn’t the only API standard
680
+ outputting JSON, GraphQL isn’t the only Graph Query Language - in fact,
681
+ you could call JSON:API a Graph Query Language as well!</em>)</p>
682
+ </blockquote>
683
+
684
+ <a class="anchor" id="rest-multiple-requests" />
685
+ <a class="header" href="#rest-multiple-requests">
686
+ <h4>
687
+ REST != Multiple Requests
688
+ </h4>
689
+ </a>
690
+
691
+ <p>Anything you could do with a single Resource, you can do with multiple
692
+ Resources. In other words, we can fetch an <code class="language-plaintext highlighter-rouge">Employee</code>, and their
693
+ <code class="language-plaintext highlighter-rouge">Positions</code> - but only <code class="language-plaintext highlighter-rouge">active</code> positions where the title starts with
694
+ <code class="language-plaintext highlighter-rouge">Eng</code>, ordered by <code class="language-plaintext highlighter-rouge">created_at</code>. This is called <strong>Deep Querying</strong>.</p>
695
+
696
+ <p>The point here is that REST doesn’t mean multiple requests. Sure, our
697
+ state is now a graph of objects instead of a single object, but there’s
698
+ no need for a wholesale revamp. In fact, we don’t need to change much at
699
+ all.</p>
700
+
701
+ <p align="center">
702
+ <img width="100%" src="/1.13/assets/img/persist.jpg" />
703
+ </p>
704
+
705
+ <p>Just as we can <strong>query</strong> multiple Resources at once, we can also
706
+ <strong>persist</strong> multiple objects at once.</p>
707
+
708
+ <p>When persisting in REST, we send the same object back to the server
709
+ alongside a verb. That verb tells us if we’re creating, updating (part
710
+ or whole), or deleting. Same thing here. Let’s specify the verb
711
+ alongside the relationship:</p>
712
+
713
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># ...</span>
714
+ <span class="ss">positions: </span><span class="p">{</span>
715
+ <span class="ss">data: </span><span class="p">{</span> <span class="ss">id: </span><span class="s2">"456"</span><span class="p">,</span> <span class="ss">type: </span><span class="s2">"positions"</span><span class="p">,</span> <span class="ss">method: </span><span class="s2">"destroy"</span> <span class="p">}</span>
716
+ <span class="p">}</span>
717
+ <span class="c1"># ...</span></code></pre></figure>
718
+
719
+ <p>There are only 4 possible verbs:</p>
720
+
721
+ <ul>
722
+ <li><code class="language-plaintext highlighter-rouge">create</code></li>
723
+ <li><code class="language-plaintext highlighter-rouge">update</code></li>
724
+ <li><code class="language-plaintext highlighter-rouge">destroy</code></li>
725
+ <li><code class="language-plaintext highlighter-rouge">disassociate</code></li>
726
+ </ul>
727
+
728
+ <p>Oh, and we’ll run everything within a transaction, throwing in
729
+ conventions for data validation as well:</p>
730
+
731
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Response code 422</span>
732
+
733
+ <span class="p">{</span>
734
+ <span class="ss">code: </span><span class="s1">'unprocessable_entity'</span><span class="p">,</span>
735
+ <span class="ss">status: </span><span class="s1">'422'</span><span class="p">,</span>
736
+ <span class="ss">title: </span><span class="s2">"Validation Error"</span><span class="p">,</span>
737
+ <span class="ss">detail: </span><span class="s2">"Name can't be blank"</span><span class="p">,</span>
738
+ <span class="ss">source: </span><span class="p">{</span> <span class="ss">pointer: </span><span class="s1">'/data/attributes/name'</span> <span class="p">},</span>
739
+ <span class="ss">meta: </span><span class="p">{</span>
740
+ <span class="ss">attribute: :name</span><span class="p">,</span>
741
+ <span class="ss">message: </span><span class="s2">"can't be blank"</span><span class="p">,</span>
742
+ <span class="ss">code: :blank</span>
743
+ <span class="p">}</span>
744
+ <span class="p">}</span></code></pre></figure>
745
+
746
+ <p>Earlier, we covered how REST was optimized for lazy-loading. Graphiti
747
+ builds on top of REST to add eager-loading, and “eager-persisting”. In
748
+ other words, we can both read and write a graph of data in a single
749
+ request.</p>
750
+
751
+ <a class="anchor" id="microservices" />
752
+ <a class="header" href="#microservices">
753
+ <h2>
754
+ (Micro) Services
755
+ </h2>
756
+ </a>
757
+
758
+ <blockquote>
759
+ <p><em>You should think hard before breaking up <a href="https://m.signalvnoise.com/the-majestic-monolith">a Majestic Monolith</a>; beware the tradeoffs. Still, if you need it, Graphiti has your back.</em></p>
760
+ </blockquote>
761
+
762
+ <p>We now have a consistent interface for queries and relationships. We
763
+ also covered how Resources connect together with Links. Put two and
764
+ two together, and you’ll see a Resource doesn’t need to be local to same
765
+ application. We can have cross-API, remote Resources as well.</p>
766
+
767
+ <p>The popular GraphQL platform Apollo does something similar with <a href="https://www.apollographql.com/docs/graphql-tools/schema-stitching.html">Schema Stitching</a>:</p>
768
+
769
+ <div class="spacer"></div>
770
+ <p align="center">
771
+ <img width="50%" src="https://user-images.githubusercontent.com/55264/52920133-506a1e00-32d7-11e9-8986-23795dbedc2c.png" />
772
+ </p>
773
+ <div class="spacer"></div>
774
+
775
+ <p>You won’t need to write code like this in Graphiti. Because we have
776
+ conventions, we can automate this stuff. Just supply a URL:</p>
777
+
778
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">EmployeeResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
779
+ <span class="n">has_many</span> <span class="ss">:positions</span><span class="p">,</span> <span class="ss">remote: </span><span class="s1">'http://example.com/api/positions'</span>
780
+ <span class="k">end</span></code></pre></figure>
781
+
782
+ <p>That’s it. Everything works the same. We can fetch an <code class="language-plaintext highlighter-rouge">Employee</code> and her <code class="language-plaintext highlighter-rouge">Position</code>s in a single
783
+ request, add additional local <em>or</em> remote Resources to the request, and
784
+ Deep Query. If you use Vandal, you’ll think it’s all the same API.</p>
785
+
786
+ <p>Microservices 🎉!</p>
787
+
788
+ <a class="anchor" id="domain-driven-design" />
789
+ <a class="header" href="#domain-driven-design">
790
+ <h2>
791
+ Don't Sleep On This One: Domain-Driven Design
792
+ </h2>
793
+ </a>
794
+
795
+ <div class="spacer"></div>
796
+ <p align="center">
797
+ <img width="80%" class="drop" src="https://user-images.githubusercontent.com/55264/48570112-9034fa00-e8d1-11e8-8f0e-f392207db355.jpg" />
798
+ </p>
799
+ <div class="spacer"></div>
800
+ <div class="spacer"></div>
801
+ <div class="spacer"></div>
802
+
803
+ <p>OK, not that DDD. <a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">This DDD</a>:</p>
804
+
805
+ <div class="spacer"></div>
806
+ <p align="center">
807
+ <img width="20%" class="drop" src="https://camo.githubusercontent.com/7812f718ee453d3153b82242f7373d6aab2d630c/68747470733a2f2f696d616765732d6e612e73736c2d696d616765732d616d617a6f6e2e636f6d2f696d616765732f492f3531735a573837736c524c2e5f53583337355f424f312c3230342c3230332c3230305f2e6a7067" />
808
+ </p>
809
+ <div class="spacer"></div>
810
+ <div class="spacer"></div>
811
+
812
+ <p>This book can be a little dry, but the concepts are excellent. Let’s
813
+ check <a href="https://stackoverflow.com/questions/1222392/can-someone-explain-domain-driven-design-ddd-in-plain-english-please/1222488#1222488">StackOverflow for a quick summary of DDD</a>:</p>
814
+
815
+ <div class="spacer"></div>
816
+
817
+ <blockquote>
818
+ <p><em>DDD is about trying to make your software a model of a real-world system or process. In using DDD, you are meant to work closely with a domain expert who can explain how the real-world system works. For example, if you’re developing a system that handles the placing of bets on horse races, your domain expert might be an experienced bookmaker.</em></p>
819
+
820
+ <p><em>Between yourself and the domain expert, you build a ubiquitous language (UL), which is basically a conceptual description of the system. The idea is that you should be able to write down what the system does in a way that the domain expert can read it and verify that it is correct. In our betting example, the ubiquitous language would include the definition of words such as ‘race’, ‘bet’, ‘odds’ and so on.</em></p>
821
+
822
+ <p><em>The concepts described by the UL will form the basis of your object-oriented design. DDD provides some clear guidance…recommends several patterns…</em></p>
823
+
824
+ <p>- <strong>Rob Knight</strong></p>
825
+ </blockquote>
826
+
827
+ <p>I trim the quote because the prescriptive patterns of DDD often
828
+ overshadow its core concepts. <a href="https://www.youtube.com/watch?v=R2IAgnpkBck">The creator of DDD, Eric Evans covered this in his
829
+ keynote last year</a>.</p>
830
+
831
+ <p>REST and Resources <a href="https://www.youtube.com/watch?v=GFhoSMD6idk">force a developer to think deeply about their
832
+ domain, and this leads to better object-oriented code</a>.
833
+ Rather than a bag of fields and types, Graphiti pushes you to think
834
+ critically about your domain.</p>
835
+
836
+ <p>A Resource is what DDD calls an <a href="https://lostechies.com/jimmybogard/2008/05/21/entities-value-objects-aggregates-and-roots/">Entity</a>. REST is about moving Domain Entities from client to server and back again.</p>
837
+
838
+ <p>You don’t <strong><em>need</em></strong> to know anything about DDD to develop in Graphiti.
839
+ But don’t sleep on it.</p>
840
+
841
+ <a class="anchor" id="clients" />
842
+ <a class="header" href="#clients">
843
+ <h2>
844
+ Clients
845
+ </h2>
846
+ </a>
847
+
848
+ <p>You can use any HTTP client with Graphiti. If you’re using JS, a simple
849
+ <code class="language-plaintext highlighter-rouge">fetch</code> will do.</p>
850
+
851
+ <p>But you may be looking for a more robust client, one that takes
852
+ advantage of Graphiti conventions. Look no further than
853
+ <a href="/1.13/js">Spraypaint</a>, our
854
+ official client heavily inspired by ActiveRecord. Query your API the
855
+ same way you query your database:</p>
856
+
857
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="c1">// All of this is chainable</span>
858
+
859
+ <span class="kd">let</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Employee</span>
860
+ <span class="p">.</span><span class="nf">where</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="p">{</span> <span class="na">prefix</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Jane</span><span class="dl">"</span> <span class="p">}</span> <span class="p">})</span>
861
+ <span class="p">.</span><span class="nf">order</span><span class="p">({</span> <span class="na">created_at</span><span class="p">:</span> <span class="dl">"</span><span class="s2">desc</span><span class="dl">"</span> <span class="p">})</span>
862
+ <span class="p">.</span><span class="nf">page</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nf">per</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
863
+ <span class="p">.</span><span class="nf">select</span><span class="p">([</span><span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">])</span>
864
+ <span class="p">.</span><span class="nf">stat</span><span class="p">({</span> <span class="na">total</span><span class="p">:</span> <span class="dl">'</span><span class="s1">count</span><span class="dl">'</span> <span class="p">})</span>
865
+ <span class="p">.</span><span class="nf">includes</span><span class="p">({</span> <span class="na">positions</span><span class="p">:</span> <span class="dl">'</span><span class="s1">department</span><span class="dl">'</span> <span class="p">})</span>
866
+ <span class="p">.</span><span class="nf">all</span><span class="p">()</span>
867
+
868
+ <span class="kd">let</span> <span class="nx">record</span> <span class="o">=</span> <span class="nx">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
869
+ <span class="nx">record</span><span class="p">.</span><span class="nx">positions</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">department</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Updated!</span><span class="dl">'</span>
870
+ <span class="k">await</span> <span class="nx">record</span><span class="p">.</span><span class="nf">save</span><span class="p">({</span> <span class="na">with</span><span class="p">:</span> <span class="p">{</span> <span class="na">positions</span><span class="p">:</span> <span class="dl">'</span><span class="s1">department</span><span class="dl">'</span> <span class="p">}</span> <span class="p">})</span></code></pre></figure>
871
+
872
+ <p>By relying on conventions we can move the URL, request, and response
873
+ under the hood, allowing you to focus on what matters - your domain.</p>
874
+
875
+ <a class="anchor" id="graphql-support" />
876
+ <a class="header" href="#graphql-support">
877
+ <h2>
878
+ GraphQL Support
879
+ </h2>
880
+ </a>
881
+
882
+ <p>OK, let’s come full circle. Let’s say some of these conventions resonate
883
+ with you, but nevertheless you’d like to develop a GraphQL API. I <strong><em>still</em></strong> think
884
+ Graphiti is your best bet, because Graphiti supports GraphQL.</p>
885
+
886
+ <p>Remember, we started with this long-hand RPC code:</p>
887
+
888
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">CreateEmployeeInput</span> <span class="p">{</span>
889
+ <span class="nl">name</span><span class="p">:</span> <span class="nb">String</span>
890
+ <span class="nx">age</span><span class="p">:</span> <span class="nx">Int</span>
891
+ <span class="p">}</span>
892
+
893
+ <span class="kd">type</span> <span class="nx">CreateEmployeePayload</span> <span class="p">{</span>
894
+ <span class="nl">employee</span><span class="p">:</span> <span class="nx">Employee</span>
895
+ <span class="p">}</span>
896
+
897
+ <span class="kd">type</span> <span class="nx">UpdateEmployeeInput</span> <span class="p">{</span>
898
+ <span class="nl">employeeId</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
899
+ <span class="nx">name</span><span class="p">:</span> <span class="nb">String</span>
900
+ <span class="nx">age</span><span class="p">:</span> <span class="nx">Int</span>
901
+ <span class="p">}</span>
902
+
903
+ <span class="kd">type</span> <span class="nx">UpdateEmployeePayload</span> <span class="p">{</span>
904
+ <span class="nl">employee</span><span class="p">:</span> <span class="nx">Employee</span>
905
+ <span class="p">}</span>
906
+
907
+ <span class="kd">type</span> <span class="nx">DestroyEmployeeInput</span> <span class="p">{</span>
908
+ <span class="nl">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
909
+ <span class="p">}</span>
910
+
911
+ <span class="kd">type</span> <span class="nx">DestroyEmployeePayload</span> <span class="p">{</span>
912
+ <span class="nl">employee</span><span class="p">:</span> <span class="nx">Employee</span>
913
+ <span class="p">}</span>
914
+
915
+ <span class="kd">type</span> <span class="nx">Employee</span> <span class="p">{</span>
916
+ <span class="nl">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span>
917
+ <span class="nx">name</span><span class="p">:</span> <span class="nb">String</span>
918
+ <span class="p">}</span>
919
+
920
+ <span class="nf">createEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">CreateEmployeeInput</span><span class="o">!</span><span class="p">):</span> <span class="nx">CreateEmployeePayload</span>
921
+ <span class="nf">updateEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">UpdateEmployeeInput</span><span class="o">!</span><span class="p">):</span> <span class="nx">UpdateEmployeePayload</span>
922
+ <span class="nf">destroyEmployee</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">DestroyEmployeeInput</span><span class="o">!</span><span class="p">):</span> <span class="nx">DestroyEmployeePayload</span>
923
+
924
+ <span class="nf">employee</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="nx">ID</span><span class="o">!</span><span class="p">):</span> <span class="nx">Employee</span></code></pre></figure>
925
+
926
+ <p>Graphiti does not need all this boilerplate. But
927
+ if we have the short-hand, that means <strong>we can automatically generate
928
+ the long-hand</strong>. We can introspect the Graphiti Resources, and use
929
+ <a href="https://github.com/rmosolgo/graphql-ruby">graphql-ruby</a> to automatically generate GraphQL code.</p>
930
+
931
+ <p>That project is <a href="https://github.com/wadetandy/graphiti-graphql">graphiti-graphql</a>. While still more of an experiment at this stage, it’s shaping up nicely. The main blockers are the conventions missing from any GraphQL API - how should we render validation errors, which sorting standard should we adopt, etc. But we have proof that if there’s a target to hit, we can autogenerate it.</p>
932
+
933
+ <p>So we <strong><em>can</em></strong> generate GraphQL via Graphiti, but should we? Maybe! Not
934
+ an unreasonable pursuit. If you’d like to go down this route, I’d love
935
+ to hear from you!</p>
936
+
937
+ <p>Still, let’s be clear what we’re missing: HTTP caching, error codes, lazy-loading, and more.</p>
938
+
939
+ <a class="anchor" id="magic" />
940
+ <a class="header" href="#magic">
941
+ <h2>
942
+ Magic
943
+ </h2>
944
+ </a>
945
+
946
+ <blockquote>
947
+ <p><em>Having that level of consistency, and working with that for
948
+ just a little while means that you can start to forget about it. And
949
+ that’s the power of conventions in general…it used to be something
950
+ you had to think about and make a decision. Well, decisions are bad.
951
+ Decisions take up your brain power, and it requires brain cycles to
952
+ consider which or the other. The more decisions you can take out of
953
+ the whole thing, the more brain power you can free up to consider the
954
+ really important things.</em></p>
955
+
956
+ <p><em>If everybody is doing the same thing in the same way, it means that
957
+ you can easily go from one application to the other, and expect the
958
+ same things to happen.</em></p>
959
+
960
+ <p>- <a href="https://www.youtube.com/watch?v=GFhoSMD6idk">“Resources on Rails”</a>, <strong>David Heinemeier Hansson</strong></p>
961
+ </blockquote>
962
+
963
+ <p>When conventions form your programming foundation, you end up with these
964
+ high-level abstractions where a few lines of code hide the underlying
965
+ complexity. Often, this is flippantly referred to as ✨”<strong>Magic</strong>“🔮</p>
966
+
967
+ <p>The thing about magic is, once you learn the trick it often comes down
968
+ to something simple: a mirror, a trick deck, a quick hand.</p>
969
+
970
+ <p>An object, moving back and forth.</p>
971
+
972
+ <p align="center">
973
+ <img width="80%" src="/1.13/assets/img/rest1.gif" />
974
+ </p>
975
+
976
+ <p>Simple concepts can often be the most powerful, and I will never get
977
+ tired of seeing this trick performed.</p>
978
+
979
+ <p>There’s plenty more we haven’t even touched on. Check out the
980
+ <a href="/1.13/guides">Guides</a>, or dive right in with the <a href="/1.13/quickstart">Quickstart</a> or <a href="/1.13/tutorial">Tutorial</a>. If
981
+ things get tricky, ask for help in our <a href="https://discord.gg/wgqkMBsSRV">Discord Chat</a>. Reach out to me
982
+ directly at <a href="mailto:richmolj@gmail.com">richmolj@gmail.com</a> or <a href="https://twitter.com/richmolj">@richmolj</a> on Twitter.</p>
983
+
984
+ <a class="anchor" id="addendum" />
985
+ <a class="header" href="#addendum">
986
+ <h2>
987
+ Addendum: Holy Shit
988
+ </h2>
989
+ </a>
990
+
991
+ <p>I can’t believe I just wrote all that. I swear, I never set out to do any of this.</p>
992
+
993
+ <p>There are a lot of big ideas here, but this project is not some ideological crusade. Three years ago I had a bug in my application, and I found it surprisingly tricky to fix. I kept pulling that string, I connected with other developers pulling their own strings, one thing led to another, and here we are. This project is nothing more than a good-faith effort to build quality software, based on my own personal experience.</p>
994
+
995
+ <p>Let’s be clear: <strong>GraphQL is awesome</strong>. It solves real problems in a
996
+ user-friendly manner, has sexy tooling and amazing libraries created
997
+ by kickass developers. There are many advantages of GraphQL not covered
998
+ here, and plenty more great ideas I’d like to steal. If you find some of
999
+ the framing here annoying, if you find yourself thinking “<em>…but what
1000
+ about X and Y?</em>”, just know I feel the same way whenever a GraphQL
1001
+ post talks about REST. It will be a continual goal of this project to
1002
+ raise the level of debate.</p>
1003
+
1004
+ <p>Same for RPC. I work for a company that made billions off of RPC. I tend
1005
+ to think REST is a better fit for the web, and RPC is a better fit for
1006
+ things like financial trading systems or video games, but plenty of
1007
+ reasonable people can disagree. Conventions and abstractions come with
1008
+ their own cost: a learning curve, indirection, lack of flexibility. If
1009
+ you prefer something more low-level, I completely understand. I’ll
1010
+ continue trying to learn from you.</p>
1011
+
1012
+ <p>Beware projects and ideologies that claim to be a solution to all your
1013
+ problems. Everything has tradeoffs. The best we can do is to avoid
1014
+ zero-sum thinking, and instead think about how we can take the best
1015
+ ideas from a variety of solutions and experiences, striving to
1016
+ continually improve. It is hard, it is exhausting, it is the right thing
1017
+ to do.</p>
1018
+
1019
+ <p>Unless you like XML. Screw XML.</p>
1020
+
1021
+ <p><br /></p>
1022
+
1023
+ <p align="center">
1024
+ <img width="100%" src="/1.13/assets/img/love-graffiti.jpg" />
1025
+ </p>
1026
+
1027
+ </div>
1028
+ </div>
1029
+ </main>
1030
+ <div class="main-footer main-footer--dark">
1031
+ <div class="container">
1032
+ <div class="row">
1033
+ <div class="col-sm-4 menu">
1034
+ <h3>Overview</h3>
1035
+ <ul>
1036
+ <li>
1037
+ <a href="/1.13/quickstart">Quickstart</a>
1038
+ </li>
1039
+ <li>
1040
+ <a href="/1.13/tutorial">Tutorial</a>
1041
+ </li>
1042
+ <li>
1043
+ <a href="/1.13/guides">Guides</a>
1044
+ </li>
1045
+ </ul>
1046
+ </div>
1047
+ <div class="col-sm-4 menu">
1048
+ <h3>Contact</h3>
1049
+ <ul>
1050
+ <li>
1051
+ <a target="_blank" href="https://discord.gg/wgqkMBsSRV">Discord Chat</a>
1052
+ </li>
1053
+ <li>
1054
+ <a href="mailto:richmolj@gmail.com">Email</a>
1055
+ </li>
1056
+ </ul>
1057
+ </div>
1058
+ <div class="col-sm-4 menu">
1059
+ <h3>Related</h3>
1060
+ <ul>
1061
+ <li>
1062
+ <a target="_blank" href="http://jsonapi.org">JSONAPI Spec</a>
1063
+ </li>
1064
+ <li>
1065
+ <a target="_blank" href="http://jsonapi-rb.org">jsonapi-rb</a>
1066
+ </li>
1067
+ <li>
1068
+ <a target="_blank" href="https://vuejs.org/">VueJS</a>
1069
+ </li>
1070
+ </ul>
1071
+ </div>
1072
+ </div>
1073
+ </div>
1074
+ </div>
1075
+
1076
+ <script type="text/javascript">
1077
+ $(function () {
1078
+
1079
+ var flipTabs = function() {
1080
+ var isTS = true;
1081
+ if (localStorage.getItem('js-lang') === 'javascript') {
1082
+ isTS = false;
1083
+ }
1084
+
1085
+ $('.code-tabs').each(function(index, el) {
1086
+ if (isTS) {
1087
+ console.log('hiding js');
1088
+ $($(el).children()[1]).hide();
1089
+ $($(el).children()[0]).show();
1090
+ } else {
1091
+ console.log('hiding ts');
1092
+ $($(el).children()[0]).hide();
1093
+ $($(el).children()[1]).show();
1094
+ }
1095
+ });
1096
+
1097
+ if (isTS) {
1098
+ $('.tab.typescript').addClass('active');
1099
+ $('.tab.javascript').removeClass('active');
1100
+ } else {
1101
+ $('.tab.typescript').removeClass('active');
1102
+ $('.tab.javascript').addClass('active');
1103
+ }
1104
+ }
1105
+
1106
+ $('.tab').click(function() {
1107
+ if ($(this).hasClass('typescript')) {
1108
+ localStorage.setItem('js-lang', 'typescript');
1109
+ } else {
1110
+ localStorage.setItem('js-lang', 'javascript');
1111
+ }
1112
+
1113
+ flipTabs();
1114
+ });
1115
+
1116
+ flipTabs();
1117
+ })
1118
+ </script>
1119
+
1120
+ </body>
1121
+ </html>