<?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>DEV Community: Charles</title>
    <description>The latest articles on DEV Community by Charles (@cgardens).</description>
    <link>https://dev.to/cgardens</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%2F474771%2Fad8146b0-665c-4508-b89c-648e921dcf5e.jpeg</url>
      <title>DEV Community: Charles</title>
      <link>https://dev.to/cgardens</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cgardens"/>
    <language>en</language>
    <item>
      <title>How to Save and Search Your Slack History on a Free Slack Plan</title>
      <dc:creator>Charles</dc:creator>
      <pubDate>Wed, 24 Feb 2021 18:01:24 +0000</pubDate>
      <link>https://dev.to/airbytehq/how-to-save-and-search-your-slack-history-on-a-free-slack-plan-m3d</link>
      <guid>https://dev.to/airbytehq/how-to-save-and-search-your-slack-history-on-a-free-slack-plan-m3d</guid>
      <description>&lt;p&gt;The &lt;a href="https://slack.com/intl/en-nc/pricing/paid-vs-free"&gt;Slack free tier&lt;/a&gt; saves only the last 10K messages. For social Slack instances, it may be impractical to upgrade to a paid plan to retain these messages. Similarly, for an open-source project like &lt;a href="https://airbyte.io/"&gt;Airbyte&lt;/a&gt; where we interact with our community through a public Slack instance, the cost of paying for a seat for every Slack member is prohibitive.&lt;/p&gt;

&lt;p&gt;However, searching through old messages can be really helpful. Losing that history feels like some advanced form of memory loss. What was that joke about Java 8 Streams? This contributor question sounds familiar—haven't we seen it before? But you just can't remember!&lt;/p&gt;

&lt;p&gt;This tutorial will show you how you can, for free, use Airbyte to save these messages (even after Slack removes access to them). It will also provide you a convenient way to search through them.&lt;/p&gt;

&lt;p&gt;Specifically, we will export messages from your Slack instance into an open-source search engine called &lt;a href="https://github.com/meilisearch/meilisearch"&gt;MeiliSearch&lt;/a&gt;. We will be focusing on getting this setup running from your local workstation. We will mention at the end how you can set up a more productionized version of this pipeline.&lt;/p&gt;

&lt;p&gt;We want to make this process easy, so while we will link to some external documentation for further exploration, we will provide all the instructions you need here to get this up and running.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Set Up MeiliSearch
&lt;/h1&gt;

&lt;p&gt;First, let's get MeiliSearch running on our workstation. MeiliSearch has extensive docs for &lt;a href="https://docs.meilisearch.com/reference/features/installation.html#download-and-launch"&gt;getting started&lt;/a&gt;. For this tutorial, however, we will give you all the instructions you need to set up MeiliSearch using Docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 7700:7700 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/data.ms:/data.ms &lt;span class="se"&gt;\&lt;/span&gt;
  getmeili/meilisearch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it!&lt;br&gt;
MeiliSearch stores data in $(pwd)/data.ms, so if you prefer to store it somewhere else, just adjust this path.&lt;/p&gt;
&lt;h1&gt;
  
  
  2. How To Replicate Your Slack Messages to MeiliSearch
&lt;/h1&gt;
&lt;h2&gt;
  
  
  a. Set Up Airbyte
&lt;/h2&gt;

&lt;p&gt;Make sure you have Docker and Docker Compose installed. If you haven’t set Docker up, follow the &lt;a href="https://docs.docker.com/desktop/"&gt;instructions here&lt;/a&gt; to set it up on your machine. Then, run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/airbytehq/airbyte.git
&lt;span class="nb"&gt;cd &lt;/span&gt;airbyte
docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run into any problems, feel free to check out our more extensive &lt;a href="https://docs.airbyte.io/getting-started"&gt;getting started&lt;/a&gt; for more help.&lt;/p&gt;

&lt;p&gt;Once you see an Airbyte banner, the UI is ready to go at &lt;a href="http://localhost:8000/"&gt;http://localhost:8000/&lt;/a&gt;. Once you have set your user preferences, you will be brought to a page that asks you to set up a source. In the next step, we'll go over how to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  b. Set Up Airbyte’s Slack Source Connector
&lt;/h2&gt;

&lt;p&gt;In the Airbyte UI, select Slack from the dropdown. We provide step-by-step instructions for setting up the Slack source in Airbyte &lt;a href="https://docs.airbyte.io/integrations/sources/slack#setup-guide"&gt;here&lt;/a&gt;. These will walk you through how to complete the form on this page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yc7LehAC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mv03xpwjegn8s98d3tne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yc7LehAC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mv03xpwjegn8s98d3tne.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the end of these instructions, you should have created a Slack source in the Airbyte UI. For now, just add your Slack app to a single public channel (you can add it to more channels later). Only messages from that channel will be replicated.&lt;/p&gt;

&lt;p&gt;The Airbyte app will now prompt you to set up a destination. Next, we will walk through how to set up MeiliSearch.&lt;/p&gt;

&lt;h2&gt;
  
  
  c. Set Up Airbyte’s MeiliSearch Destination Connector
&lt;/h2&gt;

&lt;p&gt;Head back to the Airbyte UI. It should still be prompting you to set up a destination. Select "MeiliSearch" from the dropdown. For the host field, set: &lt;a href="http://localhost:7700"&gt;http://localhost:7700&lt;/a&gt;. The api_key can be left blank.&lt;/p&gt;

&lt;h2&gt;
  
  
  d. Set Up the Replication
&lt;/h2&gt;

&lt;p&gt;On the next page, you will be asked to select which streams of data you'd like to replicate. We recommend unchecking "files" and "remote files" since you won't really be able to search them easily in this search engine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WJMd_GPQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oax8vn55frbs3nv4ocrf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WJMd_GPQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oax8vn55frbs3nv4ocrf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For frequency, we recommend every 24 hours.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Search MeiliSearch
&lt;/h1&gt;

&lt;p&gt;After the connection has been saved, Airbyte should start replicating the data immediately. When it completes you should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_14yw0bd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyrree2xstb7hprjtsul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_14yw0bd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyrree2xstb7hprjtsul.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the sync is done, you can sanity check that this is all working by making a search request to MeiliSearch. Replication can take several minutes depending on the size of your Slack instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://localhost:7700/indexes/messages/search'&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{ "q": "&amp;lt;search-term&amp;gt;" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, I have the following message in one of the messages that I replicated: "welcome to airbyte".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://localhost:7700/indexes/messages/search'&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{ "q": "welcome to" }'&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; {"hits":[{"_ab_pk":"7ff9a858_6959_45e7_ad6b_16f9e0e91098","channel_id":"C01M2UUP87P","client_msg_id":"77022f01-3846-4b9d-a6d3-120a26b2c2ac","type":"message","text":"welcome to airbyte.","user":"U01AS8LGX41","ts":"2021-02-05T17:26:01.000000Z","team":"T01AB4DDR2N","blocks":[{"type":"rich_text"}],"file_ids":[],"thread_ts":"1612545961.000800"}],"offset":0,"limit":20,"nbHits":2,"exhaustiveNbHits":false,"processingTimeMs":21,"query":"test-72"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  4. Search via a UI
&lt;/h1&gt;

&lt;p&gt;Making curl requests to search your Slack History is a little clunky, so we have modified the example UI that MeiliSearch provides in &lt;a href="https://docs.meilisearch.com/learn/tutorials/getting_started.html#integrate-with-your-project"&gt;their docs&lt;/a&gt; to search through the Slack results.&lt;br&gt;
Download (or copy and paste) this &lt;a href="https://github.com/airbytehq/airbyte/blob/master/docs/tutorials/slack-history/index.html"&gt;html file&lt;/a&gt; to your workstation. Then, open it using a browser. You should now be able to write search terms in the search bar and get results instantly!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kT12htXP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qavlp1zkkjqew3za1qf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kT12htXP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qavlp1zkkjqew3za1qf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  5. "Productionizing" Saving Slack History
&lt;/h1&gt;

&lt;p&gt;You can find instructions for how to host Airbyte on various cloud platforms &lt;a href="https://docs.airbyte.io/deploying-airbyte"&gt;here&lt;/a&gt;.&lt;br&gt;
Documentation on how to host MeiliSearch on cloud platforms can be found &lt;a href="https://docs.meilisearch.com/create/how_to/running_production.html#a-quick-introduction"&gt;here&lt;/a&gt;.&lt;br&gt;
If you want to use the UI mentioned in the section above, we recommend statically hosting it on S3, GCS, or equivalent.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How We Leveraged Singer for Our MVP</title>
      <dc:creator>Charles</dc:creator>
      <pubDate>Mon, 30 Nov 2020 20:28:00 +0000</pubDate>
      <link>https://dev.to/airbytehq/how-we-leveraged-singer-for-our-mvp-4jbj</link>
      <guid>https://dev.to/airbytehq/how-we-leveraged-singer-for-our-mvp-4jbj</guid>
      <description>&lt;p&gt;One of the (many) hard things about doing a startup is figuring out what that MVP should be. You are trading off between presenting something that is “good” enough that it gets people excited to use (or invest in) you and getting something done fast. In this article, we explore how we wrestled with this trade-off. Specifically, we explore our decisions around how to use Singer to bootstrap our MVP. It is something we get tons of questions about, and it was hard for us to figure out ourselves!&lt;/p&gt;

&lt;p&gt;When we set out to create an MVP for our data integration project, we began with this prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an OSS data integration project that includes all of Singer’s major features. In addition, it should have a UI that can be used by non-technical users and has production-grade job scheduling and tracking. &lt;/li&gt;
&lt;li&gt;Do it in a month. &lt;/li&gt;
&lt;li&gt;Use Singer to bootstrap it. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We knew from the start that in the long run, we did not want Singer to be core to the working of our platform. In the short term, however, we wanted to be able to bootstrap our integration ecosystem off of Singer’s existing taps and targets. So should we make Singer part of our core platform in the beginning to bootstrap? And if so, at what cost?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d5FT7zrG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/faal0jbi5wceqxnjphqp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d5FT7zrG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/faal0jbi5wceqxnjphqp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This picture shows the spectrum of options we considered, from wrapping a UI around Singer and relying entirely on it as our backend to shooting for our original goal of Singer as a peripheral.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Thin UI wrapper around Singer
&lt;/h1&gt;

&lt;p&gt;This felt like the “startup-y” option. We could throw Singer, a database, and a UI in a Docker container and have “something” up and running in, perhaps, days. We never tried to go with this approach because we were able to see some really big trade-offs.&lt;/p&gt;

&lt;p&gt;Pros &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just a few days in terms of amount of work needed&lt;/li&gt;
&lt;li&gt;No new code for each integration, just use Singer’s.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pretty much all throw-away code after the initial release.&lt;/li&gt;
&lt;li&gt;Because Singer taps / targets don’t declare their configurations (more on this later), there would be no way in the UI to tell the user what values they needed to provide in order to configure a source. We would only be able to accept a big json blob.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KKC9h1qR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pw534dcxj15g70vos207.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KKC9h1qR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pw534dcxj15g70vos207.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While we were going for an MVP, we did not think we would be able to get anyone interested in the first iteration. We also knew that subsequent iterations would be painful, since we would be effectively starting from scratch because the initial iteration was not a sturdy building block. We skipped this approach.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Airbyte integration configurations
&lt;/h1&gt;

&lt;p&gt;Given that we wanted to provide a UI experience that was accessible to non-data engineers, our next step was to figure out how we could make it easy to configure integrations in the UI. This meant we had to build our own configuration abstraction for integrations, because this is something that Singer does not provide (we go into more depth on this feature in the &lt;a href="https://airbyte.io/articles/data-engineering-thoughts/why-you-should-not-build-your-data-pipeline-on-top-of-singer/"&gt;first article&lt;/a&gt; in this series). &lt;/p&gt;

&lt;p&gt;This abstraction was basically a way for each integration to declare what information it needed in order to be configured. For example, a Postgres source might need a hostname, port, etc. This layer made it possible for the UI to display user-friendly forms for setting up integrations. With this approach, we could still rely on Singer as the “backend” for the platform, but we could provide a better configuration experience for the user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--87zlSTzv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x07j6o1cdjy4mjuvq8fd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--87zlSTzv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x07j6o1cdjy4mjuvq8fd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to implement this layer, we created a standardized way to declare information about an integration and how to configure it in a JsonSchema object. When someone selects an integration in the UI, it will render a form based on that JsonSchema. The user would then provide the needed information and pass it directly to the backend.&lt;/p&gt;

&lt;p&gt;This is ultimately where we started out. And everything was good for about a week…&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Dockerize Singer integrations
&lt;/h1&gt;

&lt;p&gt;Up until this point, the only thing we had to do per integration was write a JsonSchema object that declared the configuration inputs for an integration. But what if we want the form in the UI to display different fields than those that Singer taps / targets consume?&lt;/p&gt;

&lt;p&gt;The first case we ran into was in the Postgres Singer tap. That tap takes in a field called a “filter_dbs” field. This attribute restricts which databases the tap scans when being run in “discover” mode. The tap also takes in a field called ”database,” which is the name of the database from which data will be replicated. In our use case, we wanted “filter_dbs” to be populated with only a single entry, the value that the user had provided for “database.”&lt;/p&gt;

&lt;p&gt;In order to hide filter_dbs from the UI, but still populate it behind the scenes, we were going to need to write some special code that executed only when the Postgres Tap ran. But where was that code going to run? The abstraction we had was that our core platform just assumed that all integration-specific code was bundled in the Singer Tap. So we were either going to need to insert this integration-specific code into our core platform or restructure our abstraction so that we could run custom integration code that was not packaged as part of Singer.&lt;/p&gt;

&lt;p&gt;Again, we already had a rough idea of what we wanted this to look like in the long term. We imagined each integration running entirely in its own Docker container. Airbyte would handle passing messages from the container running the source to the container running the destination. We had hoped we could get to MVP without it, but ultimately, when we hit this issue, it tipped us over the edge. So we traded some time to figure out how to package Singer taps and targets into Docker containers that made it easy for us to mediate all of the interactions between the core platform and the integration running in the container.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Use the Airbyte protocol instead of the Singer protocol
&lt;/h1&gt;

&lt;p&gt;Now fast forward another couple weeks: we are on the night before we plan to do our first public launch, and nothing is working. We have 3 sources and 3 destinations, and not one of them can work with all of the others. &lt;/p&gt;

&lt;p&gt;The issue was two-fold: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We ran into &lt;a href="https://airbyte.io/articles/data-engineering-thoughts/why-you-should-not-build-your-data-pipeline-on-top-of-singer/"&gt;inconsistencies in the Singer protocol&lt;/a&gt; that made it hard to treat all Singer Taps and Targets the same way programmatically.&lt;/li&gt;
&lt;li&gt;In falling back on Singer to handle our “backend,” there were implementation details in the way Singer worked that were incompatible with the product we wanted to build.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We won’t spend a ton of time discussing these issues, because we’ve already written about them &lt;a href="https://airbyte.io/articles/data-engineering-thoughts/why-you-should-not-build-your-data-pipeline-on-top-of-singer/"&gt;here&lt;/a&gt;. So let’s just say we hit a point where we realized that we either needed to become the world’s foremost experts on the Singer protocol or focus on defining our own protocol. Since the latter already aligned with our long-term vision, we went in that direction. &lt;/p&gt;

&lt;p&gt;Ultimately, we tore out our hair and got through that night, and then for our next release we introduced our own &lt;a href="https://docs.airbyte.io/architecture/airbyte-specification"&gt;protocol&lt;/a&gt;. Even at our early stage, this was an expensive endeavour. It took one-ish engineers over a week to migrate us from the Singer protocol to our own (this felt like eons to us!).&lt;/p&gt;

&lt;h1&gt;
  
  
  Did we do it right?
&lt;/h1&gt;

&lt;p&gt;Obviously, this question is impossible to answer. After reading this article, you might have come to the conclusion that we should have built the first version of our product with Singer at the periphery of our system. And had we done that, we could have skipped the iteration of moving Singer from within our core system to the outskirts. I wouldn’t begrudge you that conclusion!&lt;/p&gt;

&lt;p&gt;Had we taken that approach, however, we would have delayed our initial release by an additional month (double time to MVP!). Getting something out early was valuable, because it gave us early feedback that what we were building was interesting to people. We made trade- offs to move fast, but still work from a base that we could iterate on quickly--pretty much the classic trade-off you think about when trying to launch an MVP. And, ultimately, we can’t draw any hard and fast rules other than to use your own judgment!&lt;/p&gt;

&lt;p&gt;The unexpected insight that we came away with, however, was that this approach allows us to learn a lot from Singer. Even having Singer be part of the core system for just a few weeks, we got a really good understanding of why they had solved certain issues the way they did. &lt;/p&gt;

&lt;p&gt;For example, when we first encountered the Singer Catalog, the use of a breadcrumb system to map metadata onto a schema felt unintuitive and needlessly complicated. The metadata and the schema were in the same parent object, so why did we need this complex system of having the metadata fields index into the schema? Couldn’t they be combined? After using it closely for a few weeks, we understood the complexities that come with configuring special behavior at a field level for deeply nested schemas. Had we gone our own way from the start, we would have learned this lesson much later (and the later we learned it, the harder it would have been to remedy). &lt;/p&gt;

&lt;p&gt;Building on top of Singer in the beginning forced us into a &lt;a href="https://en.wikipedia.org/wiki/Wikipedia:Chesterton%27s_fence"&gt;Chesterton’s Fence&lt;/a&gt; situation. Each time we wanted to do something a certain way, because we thought Singer’s approach didn’t make sense, we were forced to fully understand why Singer had done things the way it did. By doing so, we avoided mistakes we would otherwise have made. We also were able to make decisions different from Singer’s while still benefiting from its experience. All in all, we feel we made the right choice. What do you think?&lt;/p&gt;

</description>
      <category>database</category>
      <category>datascience</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why You Should NOT Build Your Data Pipeline on Top of Singer</title>
      <dc:creator>Charles</dc:creator>
      <pubDate>Mon, 30 Nov 2020 20:26:59 +0000</pubDate>
      <link>https://dev.to/airbytehq/why-you-should-not-build-your-data-pipeline-on-top-of-singer-3ei9</link>
      <guid>https://dev.to/airbytehq/why-you-should-not-build-your-data-pipeline-on-top-of-singer-3ei9</guid>
      <description>&lt;p&gt;&lt;a href="//singer.io"&gt;Singer.io&lt;/a&gt; is an open-source CLI tool that makes it easy to pipe data from one tool to another. At &lt;a href="https://airbyte.io"&gt;Airbyte&lt;/a&gt;, we spent time determining if we could leverage Singer to programmatically send data from any of their supported data sources (taps) to any of their supported data destinations (targets).&lt;/p&gt;

&lt;p&gt;For the sake of this article, let’s say we are trying to build a tool that can do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run any Singer tap or target &lt;/li&gt;
&lt;li&gt;Provide a UI for configuring and running those taps and targets&lt;/li&gt;
&lt;li&gt;Count the number of records synced in each run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the context of these goals, being able to use Singer programmatically means writing a program that can, for any integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provide a UI with instructions on what information a user needs to input in order to configure that integration (e.g., host, password, etc).&lt;/li&gt;
&lt;li&gt;take those user-provided values and execute each integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We know that the described requirements are not the use case that Singer sets out to solve, but nonetheless, we wanted to see if we could leverage Singer to bootstrap building out this case. Sure enough, we ran into some “gotchas” along the way. These gotchas illustrate some of the core primitives that a programmatic data integration tool requires.&lt;/p&gt;

&lt;h1&gt;
  
  
  Integrations do not declare their configurations
&lt;/h1&gt;

&lt;p&gt;The Singer protocol does not &lt;a href="https://github.com/singer-io/getting-started/blob/master/docs/SPEC.md#config"&gt;specify how an integration should define&lt;/a&gt; what inputs it requires. This means that, in order to use most Singer taps, you need to scour the entire implementation to figure out what properties it uses; depending on the complexity of the integration, this can be pretty painful.&lt;/p&gt;

&lt;p&gt;Some integrations help out by specifying what the configuration should look like in a &lt;a href="https://github.com/singer-io/tap-stripe"&gt;readme&lt;/a&gt; or in a &lt;a href="https://github.com/singer-io/tap-hubspot/blob/master/config.sample.json"&gt;sample config&lt;/a&gt;. Even these lead to headaches. They often just list the fields that need to be passed in but do not explain what they mean, what their format is, or how to find them (good luck trying to find all the information you need to configure your Google Ads integration!). In other cases, they only list a subset, and then you have to discover the rest by reading the integration (e.g., &lt;a href="https://github.com/singer-io/tap-salesforce"&gt;tap-salesforce&lt;/a&gt; doesn’t mention is_sandbox in the docs UPDATE: someone has now added this field in the readme with this &lt;a href="https://github.com/singer-io/tap-salesforce/commit/d21bbea93471c485c4adddfdfb9ffb3e157cc45e"&gt;PR&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;These taps are great; we have happily used all of them, but because they do not specify what is required to configure them, they can’t be used programmatically. Specifically, our program needs to know that for the Postgres tap it requires the field’s hostname and port. Without this specification, the program cannot figure out how to build a valid configuration for an integration. This configuration is expensive to shim, because it requires engineering work for every single integration!&lt;/p&gt;

&lt;h1&gt;
  
  
  No way to tell which Singer feature is compatible with which integration
&lt;/h1&gt;

&lt;p&gt;Singer has excellent &lt;a href="https://github.com/singer-io/getting-started/blob/master/docs/SPEC.md#singer-specification"&gt;documentation&lt;/a&gt; around its core protocol. It also does a nice job defining the suite of special metadata that it supports. When you start actually using Singer, however, mapping these primitives onto your integrations is difficult. For example, “replication-method” sets whether all the data from the source should be replicated (“full_table”) or just the new or updated data (“incremental”). What is unclear is which taps actually support “incremental” or “full_table” or both. &lt;/p&gt;

&lt;p&gt;Taps do not advertise, in a way that is programmatically consumable, which of these replication methods they support. Some of them mention it in their documentation, but ultimately that’s insufficient for the type of tool we want to build. So what happens when you request “incremental” from a source that only supports “full_table”? The behavior is undefined. Some taps will throw an error, some will just do a full refresh. Either way, from the point of view of the UI-based tool that we are trying to build, this isn’t really usable.&lt;/p&gt;

&lt;p&gt;The problem only gets hairier for some of the more niche metadata as well (e.g., “view-key-properties”). You either need to read the source or just try it out and see if the configuration works. This problem is adjacent to the configuration problem described in the previous section, and, similarly, requires a shim for every integration.&lt;/p&gt;

&lt;h1&gt;
  
  
  Singer’s own secret menu
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PjdV1oVi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l1hnjztiiwz9qadrsfus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PjdV1oVi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l1hnjztiiwz9qadrsfus.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re from the West coast, you might be familiar with how In-N-Out Burger &lt;a href="https://www.eater.com/2015/4/13/8382523/secret-menus-in-n-out-fast-food-burger-animal-style"&gt;popularized the “secret” menu in fast food chains&lt;/a&gt;. While charming at a drive thru, secret menus can ruin your data integration.&lt;/p&gt;

&lt;p&gt;The Singer protocol has some of its own secret menu items. For example, we were parsing each message that a tap output into JSON using the declared schema in the Singer docs. We were trying to understand really well what messages were being sent between taps and targets, so we would fail loudly if anything was sent that did not match the documented message types. Then we started getting errors on “ActivateVersionMessage.” After spelunking in the source code for a bit, we found that this message type has existed in Singer as an experimental feature since 2017. A handful of the official Singer taps use it, but there’s no guidance on what you’re supposed to do with it (I suspect it is a feature used internally at Stitch--the paid, managed solution from the creators of Singer). If you’re building something programmatic on top of Singer, your choice is to just filter it out or let it pass and hope that stuff…just works, I guess? &lt;/p&gt;

&lt;p&gt;Handling this one case is not the end of the world, but it leaves you feeling uncertain what else is lurking in the protocol that might not play well with your system.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;So to answer our original question, can we reasonably stretch the Singer to meet our product requirements? The answer is no. Doing so would require writing custom shims for every single Singer tap and target. Since the goal with data integrations is always to scale to more integrations, having to do any work on them per integration is very expensive.&lt;/p&gt;

&lt;p&gt;The Singer protocol is underspecified for this use case. This realization makes sense, because ultimately this is not the use case for which the protocol is trying to solve. Achieving these requirements depends on integrations declaring much more information about how they are configured and which features they support. We are tackling this problem at Airbyte, so if you are looking for an OSS solution that makes it easy to move your data into a warehouse, instead of trying to roll your own on top of Singer, come check us out!&lt;/p&gt;

&lt;p&gt;This article is meant to be the first in a pair of articles. The &lt;a href="https://airbyte.io/articles/data-engineering-thoughts/how-we-leveraged-singer-for-our-mvp/"&gt;second&lt;/a&gt; will explore the engineering journey that we took to figure out where Singer should fit into our system.&lt;/p&gt;

</description>
      <category>database</category>
      <category>datascience</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
