<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Antony Woods</title>
    <description>The latest articles on Forem by Antony Woods (@acron0).</description>
    <link>https://forem.com/acron0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F322610%2F7bbd17f2-1754-4e7e-893e-42e60cab28d9.jpeg</url>
      <title>Forem: Antony Woods</title>
      <link>https://forem.com/acron0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/acron0"/>
    <language>en</language>
    <item>
      <title>Implementing the feed</title>
      <dc:creator>Antony Woods</dc:creator>
      <pubDate>Fri, 02 Oct 2020 16:33:41 +0000</pubDate>
      <link>https://forem.com/workshub/implementing-the-feed-95a</link>
      <guid>https://forem.com/workshub/implementing-the-feed-95a</guid>
      <description>&lt;p&gt;If you've visited WorksHub, or one of our sites in the last few weeks then &lt;em&gt;hopefully&lt;/em&gt; you've noticed that we launched a new landing page and dashboard which revolves around what we call "the feed". In this blog I aim to talk a little bit about how we implemented the feed into our isomorphic Clojure/ClojureScript application (some of which you can see &lt;a href="https://github.com/WorksHub/client" rel="noopener noreferrer"&gt;here&lt;/a&gt; by the way). &lt;/p&gt;

&lt;h2&gt;
  
  
  The what now?
&lt;/h2&gt;

&lt;p&gt;Most of us are familiar with the concept of a 'feed' in the digital realm. It's even in the dictionary: "a facility for notifying the user of a blog or other frequently updated website that new content has been added." Splendid. That's exactly what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the bonnet
&lt;/h2&gt;

&lt;p&gt;We started out by assessing our options with respect to how we were going to power the feed, and specifically, the first question we always ask is '&lt;em&gt;are we going to build this ourselves or use an off-the-shelf solution?&lt;/em&gt;' Answering this question is complex and there are various factors to consider, including but not limited to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt; - how and where do we store the data?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability&lt;/strong&gt; - how do we ensure it's always available?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; - how do we serve the data as quickly as possible?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt; - how long will it take us to create a solution? do we have the experience in our team to even do it? how much will it cost to &lt;em&gt;buy&lt;/em&gt; a solution?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; - what are our security obligations? what are the risks and how do we mitigate these?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Answering these questions inevitably spawns more questions, until we're in a kind of giant &lt;em&gt;question tree&lt;/em&gt;(!). Cue research, planning, competitor analysis, design and a lot of chin stroking. &lt;/p&gt;

&lt;p&gt;Here at WorksHub we are light on operational resources and so our stack choices bias toward managed services or pieces that are easy to maintain. With this in mind we started with an appraisal of existing, off-the-shelf products and eventually we settled on developing a proof-of-concept using a technology called &lt;em&gt;Stream&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fworkshub.imgix.net%2F4ecc495182111ca95c0eed722cb2fa4c" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fworkshub.imgix.net%2F4ecc495182111ca95c0eed722cb2fa4c" alt="stream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getstream.io" rel="noopener noreferrer"&gt;Their website&lt;/a&gt; - and &lt;a href="https://www.works-hub.com/companies/stream/jobs" rel="noopener noreferrer"&gt;they are hiring!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of their products is literally called 'Feed' and, from their own sales material, &lt;em&gt;"Build scalable activity feeds in hours instead of months. Scale your activity feed without the notorious difficulties involved with building activity feeds on traditional databases."&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;We did some digging into Stream and the technology that runs their service. Amazingly, Thierry Schellenbach - the CEO of Stream - persists &lt;a href="https://github.com/tschellenbach/Stream-Framework" rel="noopener noreferrer"&gt;a version&lt;/a&gt; on his personal GitHub account. The repository is a gold mine of information relating to the &lt;em&gt;how's&lt;/em&gt; and &lt;em&gt;why's&lt;/em&gt; of Stream's inner design and workings. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's written in Python with Celery - fantastic choices.&lt;/li&gt;
&lt;li&gt;It's backed by Redis and/or Cassandra - both highly regarded data stores.&lt;/li&gt;
&lt;li&gt;The code is well maintained and there is extensive documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We actually spoke to Stream directly about this and they informed us that their &lt;em&gt;actual&lt;/em&gt; product&lt;/p&gt;

&lt;p&gt;These factors, combined with the knowledge that we could use the repository - code and comprehensive documentation - to answer philosophical questions made Stream a strong option and, after our proof-of-concept was successful we decided to commit to one of their managed packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not build it ourselves?
&lt;/h3&gt;

&lt;p&gt;At WorksHub we try as hard as possible to run a lean, focused technical function and this hugely influences our decisions about what we &lt;em&gt;build&lt;/em&gt; and what we &lt;em&gt;buy&lt;/em&gt;. If a commercial solution exists and is within our capacity then that is our preference, because our business is in creating opportunities for software developers, not &lt;a href="https://en.wikipedia.org/wiki/Not_invented_here" rel="noopener noreferrer"&gt;reinventing the wheel.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Revving the engine
&lt;/h2&gt;

&lt;p&gt;Well, there's a fair amount of code that exists between Stream's service and our frontend. Our backend (written in Clojure) is responsible for generating 'activites' - things that appear on the feed - and relaying those to Stream. From trivial activities, such as 'new job' or 'new article' (possibly where you first learned about this one?), to more complex activities such as deciding whether a job or an issue is 'trending'. We handle all of that ourselves and then, upon request, Stream sends us a paginated, filtered, joined collection of these activities which are destined for either a public landing page, a particular tag page or a user's dashboard. What you get back depends on what exactly you ask for. Internally there are several (hundred) individual 'feeds' which are stitched together - based on certain rules about who's following who - and provided as a single list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffunctionalworks-backend--prod.s3.amazonaws.com%2Flogos%2Fd811169b7931ca02234b6541bd5c8fc1" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffunctionalworks-backend--prod.s3.amazonaws.com%2Flogos%2Fd811169b7931ca02234b6541bd5c8fc1" alt="blog4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As an example, anything tagged with 'Clojure' is added to a dedicated Clojure feed. If you visit &lt;a href="https://functional.works-hub.com/feed?tags=clojure%3Atech" rel="noopener noreferrer"&gt;https://functional.works-hub.com/feed?tags=clojure%3Atech&lt;/a&gt; you can view this feed in isolation. When you visit the Functional Works landing page however, you see a feed that 'follows' Clojure as well as a bunch of other feeds such as Elixir, Scala, F# etc. Similarly, other landing pages (Javascript, Golang etc) follow feeds that are relevant to them and their respective ecosystems (although there's actually a lot of overlap).&lt;/p&gt;

&lt;p&gt;To facilitate our backend communicating with Stream we introduced a new library, &lt;a href="https://github.com/WorksHub/shyvana" rel="noopener noreferrer"&gt;Shyvana&lt;/a&gt;, which is a wrapper around Stream's own &lt;a href="https://github.com/getstream/stream-java" rel="noopener noreferrer"&gt;stream-java&lt;/a&gt; library. This means we can avoid additional Java interop in our backend...and, in the process make it easier for any other Clojure developers to interact with Stream!&lt;/p&gt;

&lt;h2&gt;
  
  
  Racing stripes
&lt;/h2&gt;

&lt;p&gt;In case you haven't wandered over to our &lt;a href="https://github.com/WorksHub/client" rel="noopener noreferrer"&gt;client repository&lt;/a&gt; yet, we use an &lt;a href="https://en.wikipedia.org/wiki/Isomorphic_JavaScript" rel="noopener noreferrer"&gt;isomorphic&lt;/a&gt; ClojureScript framework called &lt;a href="https://github.com/day8/re-frame" rel="noopener noreferrer"&gt;re-frame&lt;/a&gt; in order to build the frontend. The benefit of such an approach is that we get all the "good stuff" of single-page applications (modelling, interactivity, dynamism, etc) along with all the "good stuff" of server-side rendered pages (SEO, performance, caching etc). Stay tuned for an article about this in the future!&lt;/p&gt;

&lt;p&gt;We use GraphQL (via our own wrapper library, &lt;a href="https://github.com/WorksHub/leona" rel="noopener noreferrer"&gt;Leona&lt;/a&gt; - do you recognise the references?) to retrieve data from the backend. We make an effort handle the data from Stream &lt;em&gt;as little as possible&lt;/em&gt; before sending it to the client so as to minimize pressure on our side. We also cache recent results so that we can avoid excessive requests. This all means that whether the request originates from the client as XHR or whether it originates within the server as part of a server-side render, the request handling, the cache coverage and any data transformation is actually performed by the same Clojure code, compiled into two separate environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Choosing Stream as a technology to help us deliver this feature was a critical decision and allowed our technical team to focus on the domain problems we were trying to solve - delivering bespoke, dynamic content for software engineers to help them learn, grow and find jobs they want - rather than get bogged down with complex infrastructure and operations. The choice was heavily motivated by our ability to fully understand the Stream product as a result of the fact that it began life as an open source project.&lt;/p&gt;

&lt;p&gt;As an honourable mention, using a power combo such as Clojure and ClojureScript allowed us to move very quickly and prove out the assumptions we had made about how the feeds would compose and work with our tag system. The decision to commit to this ecosystem is justified on regular basis.&lt;/p&gt;

&lt;p&gt;As far as the end product is concerned I truly hope the results speak for themselves 🎉 and we will continue iterating and improving the feed. In case you have any feedback, &lt;a href="//mailto:antony@functionalworks.com"&gt;please let me know.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jobs</category>
      <category>reframe</category>
      <category>clojurescript</category>
      <category>isomorphism</category>
    </item>
  </channel>
</rss>
