<?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: Joseph Matthias Goh</title>
    <description>The latest articles on DEV Community by Joseph Matthias Goh (@zephinzer).</description>
    <link>https://dev.to/zephinzer</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%2F128582%2Ff828784a-8f18-4b7b-aa29-ae51b2044141.jpeg</url>
      <title>DEV Community: Joseph Matthias Goh</title>
      <link>https://dev.to/zephinzer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zephinzer"/>
    <language>en</language>
    <item>
      <title>Kubernetes in Five Minutes</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Mon, 30 Nov 2020 13:59:09 +0000</pubDate>
      <link>https://dev.to/zephinzer/kubernetes-in-five-minutes-31m6</link>
      <guid>https://dev.to/zephinzer/kubernetes-in-five-minutes-31m6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://joeir.medium.com/kubernetes-in-five-minutes-18aa4101544f" rel="noopener noreferrer"&gt;Also available on my Medium&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A self-inflicted challenge because I heard people dig Kubernetes in 2020
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“If you can’t explain it simply enough, you don’t understand it well enough”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Challenge accepted. Here’s my attempt to explain native components of Kubernetes in five minutes with the objective of covering concepts and resource types that will enable you to &lt;del&gt;talk about Kubernetes like you understand it&lt;/del&gt; understand Kubernetes and deploy applications at a basic level.&lt;/p&gt;

&lt;p&gt;As a pretext, I’ve found it useful to think of Kubernetes-related work not as a collection of verbose YAML files defining strange &lt;code&gt;kind&lt;/code&gt;s of resources, but as a &lt;strong&gt;&lt;em&gt;set of abstractions representing common design patterns in application deployment&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To me, the areas that Kubernetes natively accounts for in logical — &lt;em&gt;imo&lt;/em&gt; — order are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hardware&lt;/strong&gt; – Node&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration&lt;/strong&gt; – Deployment, Job, CronJob, StatefulSet, DaemonSet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration&lt;/strong&gt; – ConfigMap, Secret&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence&lt;/strong&gt; — PersistentVolumeClaim, PersistentVolume&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution&lt;/strong&gt; – Pod, ReplicaSet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Control&lt;/strong&gt; – Namespace, ServiceAccount, Role, ClusterRole&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exposure&lt;/strong&gt; – Service, Ingress&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;All software has one fundamental dependency: a machine to run on. We need &lt;strong&gt;hardware&lt;/strong&gt; whether it’s our own computers or someone else’s and this abstraction comes in the form of a Node resource type.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/architecture/nodes/" rel="noopener noreferrer"&gt;Node&lt;/a&gt;&lt;/strong&gt;: Represents a logical machine/VM (eg. your computer, ec2 instance, a droplet).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Orchestration
&lt;/h2&gt;

&lt;p&gt;After we’ve defined our hardware, we need a plan on how to deploy our application. Will it be a single instance? Do we need maybe two? When we need to update our application, should it be done one by one or all at once? Do we want it to be deployed across all our Nodes? Should it be run once every day at 3 AM? We need to &lt;strong&gt;orchestrate&lt;/strong&gt; the deployment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Deployment&lt;/a&gt;&lt;/strong&gt;: Long-standing workloads (eg. a server)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/" rel="noopener noreferrer"&gt;Job&lt;/a&gt;&lt;/strong&gt;: One-off workload (eg. a shell script/database migration task)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/" rel="noopener noreferrer"&gt;CronJob&lt;/a&gt;&lt;/strong&gt;: Periodically-run one-off workload (eg. data sync task)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/" rel="noopener noreferrer"&gt;StatefulSet&lt;/a&gt;&lt;/strong&gt;: Workloads that require a readable/writable data volume (hard disk) that persists beyond restarts and is not shared amongst other instances of the same application. Use cases include services that implement eventual consistency (eg. transactional databases)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/" rel="noopener noreferrer"&gt;DaemonSet&lt;/a&gt;&lt;/strong&gt;: Workloads that should be distributed across all targeted Nodes (eg. log collectors, system monitors, security software)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;So we’ve got a plan, but applications tend to be a fickle bunch and also need to be told certain things at runtime instead of plan-time; Things such as which network interface to bind to, which port to listen on, which API keys to use &lt;em&gt;et cetera&lt;/em&gt;. While these things can technically be hard-coded, best-practices of 2020 generally suggest they should not. So we need a way to &lt;strong&gt;configure&lt;/strong&gt; our application at runtime.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/configuration/configmap/" rel="noopener noreferrer"&gt;ConfigMap&lt;/a&gt;&lt;/strong&gt;: Stores configuration for mounting as environment variables or read-only files (eg. &lt;code&gt;SERVER_PORT=8080&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/" rel="noopener noreferrer"&gt;Secret&lt;/a&gt;&lt;/strong&gt;: Fundamentally the same but stored in Base64 encoded text and used for values that should not be stored in plaintext (eg. &lt;code&gt;GITHUB_API_TOKEN=😱&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Persistence
&lt;/h2&gt;

&lt;p&gt;Now what if our application handles files and requires a hard disk that it can read and write from across different versions and instances? We need our data to &lt;strong&gt;persist&lt;/strong&gt; by providing our application’s system with a hard disk (AKA volume in technical terms).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/" rel="noopener noreferrer"&gt;PersistentVolume&lt;/a&gt;&lt;/strong&gt;: A logical “hard drive” (eg. Seagate 2TB, EBS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolumeclaim" rel="noopener noreferrer"&gt;PersistentVolumeClaim&lt;/a&gt;&lt;/strong&gt;: Binds a PersistentVolume to a Pod. This is more of a virtual construct: think of it as the act of mounting a hard drive. A PVC defines that intention to mount a hard drive and expose its filesystem for an application to use.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Execution
&lt;/h2&gt;

&lt;p&gt;So we’ve defined how our application will be deployed and configured, and we’ve given it some hard disk space to use. All that’s left now is to &lt;strong&gt;execute&lt;/strong&gt; the application.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/" rel="noopener noreferrer"&gt;Pod&lt;/a&gt;&lt;/strong&gt;: One instance of your application (eg. npm start, go run ./...)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/" rel="noopener noreferrer"&gt;ReplicaSet&lt;/a&gt;&lt;/strong&gt;: Maintains the desired count of your application instances.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Access Control
&lt;/h2&gt;

&lt;p&gt;But what if someone accidentally introduced a nasty virus into our application (somehow)? In an enterprise environment with compliance teams nagging at the whole DevOps thingy, we need to ensure that applications can only access resources that it needs to access. We on the other hand, need &lt;strong&gt;access control&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/?spm=a2c65.11461447.0.0.15d87863YiFv0v" rel="noopener noreferrer"&gt;Namespace&lt;/a&gt;&lt;/strong&gt;: Defines a virtual boundary within a cluster for access control mechanisms to be implemented on top of. Think about these like browser tabs: an open Facebook tab shouldn’t know what you’re googling in another tab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/" rel="noopener noreferrer"&gt;ServiceAccount&lt;/a&gt;&lt;/strong&gt;: Defines a virtual user that can be assigned a Role or ClusterRole which contains a set of permissions scoped to a set of resources (eg. your user login on your machine)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" rel="noopener noreferrer"&gt;Role&lt;/a&gt;&lt;/strong&gt;: Defines namespace-scoped resource access permissions. Linked to a ServiceAccount with namespace-scoped access via a &lt;strong&gt;&lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#rolebinding-v1-rbac-authorization-k8s-io" rel="noopener noreferrer"&gt;RoleBinding&lt;/a&gt;&lt;/strong&gt; resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" rel="noopener noreferrer"&gt;ClusterRole&lt;/a&gt;&lt;/strong&gt;: Defines cluster-wide resource access permissions. Linked to a ServiceAccount with cluster-wide access via a &lt;strong&gt;&lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#rolebinding-v1-rbac-authorization-k8s-io" rel="noopener noreferrer"&gt;ClusterRoleBinding&lt;/a&gt;&lt;/strong&gt; resource&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Exposure
&lt;/h2&gt;

&lt;p&gt;Finally, there’s no use of an application that no one can access. After we’ve made the application happy and it’s running as expected, we expose it to the rest of the network/public internet for users and other services to access it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/" rel="noopener noreferrer"&gt;Service&lt;/a&gt;&lt;/strong&gt;: Exposes a workload to the cluster. Think running npm start on a server application and your application being available on &lt;code&gt;localhost:3000&lt;/code&gt;. Behind the scenes, your application is binding to a port on your computer. A Service defines that binding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" rel="noopener noreferrer"&gt;Ingress&lt;/a&gt;&lt;/strong&gt;: Exposes a Service to outside the cluster. Ever tried asking your co-worker to access your &lt;code&gt;localhost:3000&lt;/code&gt;? Chances are you’d have ended up using &lt;code&gt;ngrok&lt;/code&gt; or a similar tunneling software. An Ingress is basically an &lt;code&gt;ngrok&lt;/code&gt; that exposes your &lt;code&gt;localhost:3000&lt;/code&gt; so it becomes accessible to a larger network.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Linking It All Up —Some Common Deployment Patterns
&lt;/h2&gt;

&lt;p&gt;They said a picture is worth a thousand words. So here’s some diagrams to visualise how all the above links up.&lt;/p&gt;

&lt;h3&gt;
  
  
  An HTTP-based API server
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqtp1wkelheb59zwf5ngs.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqtp1wkelheb59zwf5ngs.png" alt="k8s-in-5-mins-http-server"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  A periodic job
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuiacnl4c0jdx45pm4m68.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuiacnl4c0jdx45pm4m68.png" alt="k8s-in-5-mins-periodic-job"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  An eventually consistent database system
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftd2r0u5s20fmk2bn3l79.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftd2r0u5s20fmk2bn3l79.png" alt="k8s-in-5-mins-db"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  A cloud-monitoring agent
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyq26obpezx1p5c5l6f7o.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyq26obpezx1p5c5l6f7o.png" alt="k8s-in-5-mins-cloud-monitor"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h1&gt;
  
  
  Whew!
&lt;/h1&gt;

&lt;p&gt;I hope that was somehow useful for you. If it was, ❤️s are always appreciated for reach, and feel free to follow to get pinged when I publish similar-ish pieces.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you would also like to support my writing with actual cash and if you have a paid Medium account, consider reading this article on Medium and giving some 👏s there &amp;lt;3&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>How we use Google Sheets API to Manage Our Notification Banners</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Fri, 24 Apr 2020 10:52:39 +0000</pubDate>
      <link>https://dev.to/mcf/how-we-use-google-sheets-api-to-manage-our-notification-banners-3jn2</link>
      <guid>https://dev.to/mcf/how-we-use-google-sheets-api-to-manage-our-notification-banners-3jn2</guid>
      <description>&lt;p&gt;If you've visited our site before, you'd have noticed that banner at the top of our page:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqcx5xadx81grglmeqyq3.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqcx5xadx81grglmeqyq3.jpg" alt="The header banner at the top of MyCareersFuture.SG"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or you might have noticed the inline banner linking to the &lt;strong&gt;#SGUnitedJobs&lt;/strong&gt; virtual career fair site:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1te0zfbflm3tt2tlx54o.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1te0zfbflm3tt2tlx54o.jpg" alt="The inline banner in the middle of our landing page at MyCareersFuture.SG"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was a time when we didn't have any of these, so what happened?&lt;/p&gt;

&lt;h1&gt;
  
  
  The Problem Domain
&lt;/h1&gt;

&lt;p&gt;The header banner has been in-place for some time now, and it was created due to some needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We wanted a way to notify users about new features we've released&lt;/li&gt;
&lt;li&gt;We wanted a way to notify users of scheduled downtimes because of systems that we're integrating with&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More recently, to support whole-of-government efforts to reduce the impact on our workforce, the agency we're servicing - Workforce Singapore - decided to launch an &lt;strong&gt;#SGUnitedJobs&lt;/strong&gt; virtual career fair. And there was a slight issue.&lt;/p&gt;

&lt;p&gt;In the media release, the press were quoted the words "MyCareersFuture". This led to the general public landing on our site instead of the virtual career fair's site, resulting in &lt;strong&gt;a need to redirect users to the correct site&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This meant some updates to our landing page notification/updates elements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The header banner needed to be clickable&lt;/li&gt;
&lt;li&gt;An in-page element needed to be created&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In addition to addressing users' problems, our solution also needed to accomodate use cases by our business elements, and they needed to be able to &lt;strong&gt;enable text-changes to be made without a new system deployment&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Solution
&lt;/h1&gt;

&lt;p&gt;We begin with the end.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6d0eto3lh2ex41i32bpn.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6d0eto3lh2ex41i32bpn.jpg" alt="An image of the sheet in Google Sheets that our business elements use to configure the banners"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google sheets for us was a nice interface between business and technical elements. Business users could enter in information as it came along without interfering with the code-level development using a spreadsheet, with the spreadsheet being able to run custom-code to generate a JSON output that our system could consume.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F21lio8b6veemafmiw56f.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F21lio8b6veemafmiw56f.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Wait, can Google sheets do that?"&lt;/em&gt; I hear you ask.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up Google Sheets as a JSON API
&lt;/h1&gt;

&lt;p&gt;Let's set up a simple Google Sheets that you can use as an API that's similar in nature to what we've done.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In MCF's backend, we consume data from Google Sheets, run validations on it before throwing it back out as a &lt;code&gt;.json&lt;/code&gt; file which we push to our CDNs so that these can be consumed by our users without placing a load on our servers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Create a new spreadsheet
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://drive.google.com" rel="noopener noreferrer"&gt;https://drive.google.com&lt;/a&gt; and create a new spreadsheet. Maybe create a table like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Username&lt;/th&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;JSON Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Joseph&lt;/td&gt;
&lt;td&gt;joseph&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:j@seph.com"&gt;j@seph.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Matthias&lt;/td&gt;
&lt;td&gt;matthias&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:m@tthias.com"&gt;m@tthias.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Goh&lt;/td&gt;
&lt;td&gt;goh&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:g@h.com"&gt;g@h.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;PROTIP: The table above works like copypasta with Google Sheets if you copy it properly&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Publish to the web
&lt;/h2&gt;

&lt;p&gt;Go to the top navigation bar, and access &lt;strong&gt;File&lt;/strong&gt; &amp;gt; &lt;strong&gt;Publish to the web&lt;/strong&gt;. Confirm that &lt;strong&gt;Link&lt;/strong&gt; is selected and select &lt;strong&gt;Sheet1&lt;/strong&gt;. Change the type to &lt;strong&gt;Comma-separated values (.csv)&lt;/strong&gt;. Click the &lt;strong&gt;Publish&lt;/strong&gt; button and say OK.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgqpjyegeawvuc67tjc9q.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgqpjyegeawvuc67tjc9q.jpg" alt="(Are you sure you want to publish this selection?) Yasss, yass, yas"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A link should be provided to you. Test it out by pasting it into the address bar of your page. A &lt;code&gt;.csv&lt;/code&gt; file should be downloaded. Opening it up should reveal (if you've entered in the information as-if from above):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID,Name,Username,Email
1,Joseph,joseph,j@seph.com
2,Matthias,matthias,m@tthias.com
3,Goh,goh,g@h.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Making it JSON
&lt;/h2&gt;

&lt;p&gt;We'll be inserting the JSON as values in the empty &lt;strong&gt;JSON Output&lt;/strong&gt; column you've copied above using the Script Editor. In the header navigation menu, go to &lt;strong&gt;Tools* &amp;gt; **Script editor&lt;/strong&gt;. A new &lt;code&gt;Code.gs&lt;/code&gt; should be waiting for you.&lt;/p&gt;

&lt;p&gt;Overwrite the generated code and paste in the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onEdit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editedRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheetName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sheet1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createUserJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userJSON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Google Script is essentially JavaScript. Also, if you've changed your sheet name, you might want to replace the &lt;code&gt;"Sheet1"&lt;/code&gt; in the switch-case branch to the name of your sheet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The above code is triggered on an edit to the sheet. It checks whether the edited sheet is named &lt;strong&gt;Sheet1&lt;/strong&gt; and if it is, generates a JSON string and pastes it into the 5th column of the row being edited. Save it by going to &lt;strong&gt;File&lt;/strong&gt; &amp;gt; &lt;strong&gt;Save&lt;/strong&gt;. The name shouldn't matter.&lt;/p&gt;

&lt;p&gt;Go back to your sheets and edit one of the existing values. On removing your focus from that cell, you should see a JSON value appear in the 5th column.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Consuming the JSON
&lt;/h2&gt;

&lt;p&gt;Retrieve the link from the &lt;strong&gt;Publish to the web&lt;/strong&gt; stage. We're going to use that to retrieve our JSON outputs. Your original link should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://docs.google.com/spreadsheets/d/e/2PACX-XXX/pub?gid=YYY&amp;amp;single=true&amp;amp;output=csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm you've done an edit so that the JSON appears and then do a &lt;code&gt;curl&lt;/code&gt; to see what it looks like now. In your terminal:&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;'https://docs.google.com/spreadsheets/d/e/2PACX-XXX/pub?gid=YYY&amp;amp;single=true&amp;amp;output=csv'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should receive something similar to the following as your response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID,Name,Username,Email,JSON Output
1,Joseph,joseph,j@seph.com,"{""id"":1,""name"":""Joseph"",""username"":""joseph"",""password"":""j@seph.com""}"
2,Matthias,matthias,m@tthias.com,
3,Goh,goh,g@h.com,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're almost there!&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tying It All Up
&lt;/h2&gt;

&lt;p&gt;Now that we can generate the JSON, go ahead and update the other rows so that all of them have a value in the JSON Output column.&lt;/p&gt;

&lt;p&gt;In this step, we'll link all the JSON Output values into a new sheet which we can use as the data source in a service.&lt;/p&gt;

&lt;p&gt;Add a &lt;strong&gt;New Sheet&lt;/strong&gt; by clicking the plus (&lt;code&gt;+&lt;/code&gt;) symbol at the bottom left of the existing spreadsheet. Name it &lt;strong&gt;Output&lt;/strong&gt;. We'll be writing the aggregated JSON Output to row 1 column 1 of this sheet.&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Tools&lt;/strong&gt; &amp;gt; &lt;strong&gt;Script editor&lt;/strong&gt; once again and modify the script there  so that it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onEdit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editedRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheetName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sheet1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createUserJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// &amp;gt; diff starts here&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonOutputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSheet&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;E2:E&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValues&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aggergatedUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nx"&gt;jsonOutputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;aggergatedUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActive&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Output&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputCell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outputSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;outputCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aggergatedUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="c1"&gt;// / diff ends here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userJSON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we just added basically retrieves the value of all rows in column E which in the sample sheet we created corresponds to the column &lt;strong&gt;JSON Output&lt;/strong&gt;. It then transforms the retrieved values into an array object before converting it back into a string using &lt;code&gt;JSON.stringify&lt;/code&gt;. The &lt;code&gt;null, 2&lt;/code&gt; arguments in the &lt;code&gt;JSON.stringify&lt;/code&gt; call basically indicates to format the JSON in a human readable way (&lt;em&gt;see &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify" rel="noopener noreferrer"&gt;JSON.stringify documentation on MDN&lt;/a&gt; if you're interested in what exactly it does&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;After you've made the change, save the GoogleScripts project, head back to your sheet and go ahead make a modification to one of the columns to trigger an output to the &lt;strong&gt;Output&lt;/strong&gt; sheet in your spreadsheet.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhi5u3ohyq35fstwk1gj4.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhi5u3ohyq35fstwk1gj4.jpg" alt="The final JSON output in the Output sheet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;File&lt;/strong&gt; &amp;gt; &lt;strong&gt;Publish to the web&lt;/strong&gt; once again, this time selecting the &lt;strong&gt;Output&lt;/strong&gt; sheet and selecting &lt;strong&gt;Tab-separated values (.tsv)&lt;/strong&gt; as the type. In the &lt;strong&gt;Published content and settings&lt;/strong&gt; section, ensure that &lt;strong&gt;Output&lt;/strong&gt; is also being published and that the checkbox with &lt;strong&gt;Automatically republish when changes are made&lt;/strong&gt; is also checked.&lt;/p&gt;

&lt;p&gt;Copy the provided link which look like (sensitive values are masked with &lt;code&gt;XXX&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://docs.google.com/spreadsheets/d/e/2PACX-XXX/pub?gid=YYY&amp;amp;single=true&amp;amp;output=tsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To modify this such that it will always return just the first row and column, append a &lt;code&gt;&amp;amp;range=A1&lt;/code&gt; so that the final link looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://docs.google.com/spreadsheets/d/e/2PACX-XXX/pub?gid=YYY&amp;amp;single=true&amp;amp;output=tsv&amp;amp;range=A1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test this out by running a &lt;code&gt;curl&lt;/code&gt; with it which would be what your Request module would be doing if it calls this URL:&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="nt"&gt;-vv&lt;/span&gt; &lt;span class="s1"&gt;'https://docs.google.com/spreadsheets/d/e/2PACX-XXX/pub?gid=YYY&amp;amp;single=true&amp;amp;output=tsv&amp;amp;range=A1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test that it's valid JSON, we can pipe it to a tool called &lt;code&gt;jq&lt;/code&gt; that can help us validate it:&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="nt"&gt;-vv&lt;/span&gt; &lt;span class="s1"&gt;'https://docs.google.com/spreadsheets/d/e/2PACX-XXX/pub?gid=YYY&amp;amp;single=true&amp;amp;output=tsv&amp;amp;range=A1'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're done. Aren't we?&lt;/p&gt;

&lt;h2&gt;
  
  
  6. What if something goes wrong?
&lt;/h2&gt;

&lt;p&gt;As with all code, things can and will go wrong during development, but the awesome news is that Google provides us with a page where we can view errors in our script and this can be found on the &lt;code&gt;Code.gs&lt;/code&gt; page through the file navigation menu via &lt;strong&gt;View&lt;/strong&gt; &amp;gt; &lt;strong&gt;Executions&lt;/strong&gt; which should open the script's Google Apps Script dashboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that only calls to &lt;code&gt;console.error&lt;/code&gt; go through to this dashboard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Looks like we're done for real!&lt;/p&gt;




&lt;p&gt;If you like what you just read, don't forget to leave some reactions/comments so we know this has been interesting for you- and do consider following us for more insights into tools we use and our development processes.&lt;/p&gt;

&lt;p&gt;Cheers and till next time!&lt;/p&gt;

</description>
      <category>api</category>
      <category>googlesheets</category>
      <category>business</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Conducting The Technical Interview: What does it take to conduct an awesome technical interview?</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Fri, 17 Apr 2020 04:12:54 +0000</pubDate>
      <link>https://dev.to/zephinzer/conducting-the-technical-interview-what-does-it-take-to-conduct-an-awesome-technical-interview-3ih8</link>
      <guid>https://dev.to/zephinzer/conducting-the-technical-interview-what-does-it-take-to-conduct-an-awesome-technical-interview-3ih8</guid>
      <description>&lt;p&gt;I've been conducting interviews for software engineers together with a couple of like-minded colleagues for some time now; and I thought it'd be nice to share my learnings and experiences from this journey of trying to be both a nice human, as well as a reliable gatekeeper for the team.&lt;/p&gt;

&lt;p&gt;This piece that follows will try to provide some insights to an ever-improving attempt to answer the question &lt;strong&gt;&lt;em&gt;"what does it take to conduct an awesome technical interview?"&lt;/em&gt;&lt;/strong&gt; I'll be covering how the interview process for software engineers at my tribe and product team have evolved over the years, followed by some principles and methodologies which I've found to be constructive when on the interviewing panel.&lt;/p&gt;

&lt;p&gt;The context of what follows will be within my experience interviewing software engineers at the Agile Consulting &amp;amp; Engineering (ACE) tribe, and eventually the &lt;a href="https://www.mycareersfuture.sg/"&gt;MyCareersFuture (MCF)&lt;/a&gt; product team. We're all under &lt;a href="https://www.hive.gov.sg/"&gt;Government Digital Services (GDS) aka Hive&lt;/a&gt;, the "digital transformation" department of GovTech, which is where you should be applying to should you want in (my direct email also follows after the article 😊)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post is also published &lt;a href="https://medium.com/@joeir/4a3697fe97b2?source=friends_link&amp;amp;sk=916c97f9cd30b02efcf80f2cf0ef6cfa"&gt;on Medium at this URL&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And since I'm &lt;em&gt;kinda-sorta-ish&lt;/em&gt; a fan of Simon Sinek, let's &lt;a href="https://en.m.wikipedia.org/wiki/Start_With_Why"&gt;start with why&lt;/a&gt; I became interested in improving the interview process for engineers-&lt;/p&gt;

&lt;h1&gt;
  
  
  🤔 Starting with Why
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;"In organisations, people are our greatest assets."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;People are not assets.&lt;/strong&gt; People are literally the organisation. And how do people get into an organisation? The interview, of course!&lt;/p&gt;

&lt;p&gt;The interview process when done with intent presents opportunities for both the applicant as well as the organisation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The interview is an applicant's only reliable signal as to what they're really signing up for beyond the foosball tables and free-flow bars&lt;/li&gt;
&lt;li&gt;The interview is the organisation's best-attempt at making an informed decision&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With recent shifts in perception of technical expertise from being a cost-centre to being an enabler, the interview is as important as the fancy perks organisations seem to offer these days to attract the best-of-the-best.&lt;/p&gt;

&lt;p&gt;First impressions go both ways, and it'd be a pity if we had a unicorn sitting in front of us, and they were the ones saying no thanks. It'd also be a greater pity if we had that unicorn sitting in front of us, and we couldn't recognise them.&lt;/p&gt;

&lt;p&gt;To me, it was the initial culture at GDS which enabled the larger organisation to be able to reach the level of capability and community standing we are at today (just ask any software guy from the 80s what they thought about IDA); and sustaining culture requires dedicated intent: intent to hire for a team that inspires and challenges each other, intent to hire not just to fill a headcount or seem full-strength on paper.&lt;/p&gt;

&lt;p&gt;I want to work with awesome people, and being part of the interviews and giving it my best-shot was one way I believed would help to sustain the culture I was enamoured with.&lt;/p&gt;

&lt;p&gt;TODO: image&lt;/p&gt;

&lt;p&gt;And we begin with how it all started…&lt;/p&gt;

&lt;h1&gt;
  
  
  🐒 Evolution of The Technical Interview
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Episode I: The Algorithm Menace (circa 2016)
&lt;/h2&gt;

&lt;p&gt;When I first came onboard, our interview process looked like something out of a 2010s tech-giant in Singapore. There was the pre-invitation dance with the hiring manager where I was like &lt;em&gt;"government *cannot* be cool,"&lt;/em&gt; and he was like &lt;em&gt;"oh come see it for yourself!"&lt;/em&gt;; so I did one fine Tuesday at 9:30AM and I was like &lt;em&gt;"oh indeed it is cool these days,"&lt;/em&gt; and the rest is history.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Please set aside three hours for this assignment,"&lt;/em&gt; came the e-mail which contained a link to HackerEarth and one of those algorithmic technical assessments.&lt;/p&gt;

&lt;p&gt;This was followed by a face-to-face chat where I met more senior members from the team that I'd be working with, a live-programming session to make sure I could actually code, a team-lunch for assessing culture-fit, and finally the boss-stage interview with the hiring manager and the HR team.&lt;br&gt;
Fortunately I was deemed worthy and joined in September of 2016.&lt;/p&gt;

&lt;p&gt;This interview format worked back then when we were at about 30 people and not in any rush to scale up.&lt;/p&gt;

&lt;p&gt;Problems were soon exposed when the need to scale came upon us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Episode II: Attack of the Applicants (Scaling up – circa '17-'18)
&lt;/h2&gt;

&lt;p&gt;The GDS department started gaining exponential traction within the government with project requests coming in quicker than we could handle. We had to scale; and our hiring manager knew it. This resulted in many candidates being dropped into our Trello board "&lt;em&gt;for (y)our consideration&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;As we continued with the interviews as we'd always have, we observed three things that influenced us to want to make changes to the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Being proficient at algorithms wasn't indicative of overall engineering talent.&lt;/strong&gt; While the algorithmic technical assessment was a good filter for technical and conceptual depth, it didn't assess for the kind of skills we needed on a daily basis which was more engineer-&lt;em&gt;ish&lt;/em&gt;/systems-level type of work. We needed a more relevant way to assess how someone would fare doing what we do on a daily basis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More experienced candidates tend to be filtered out by algorithmic technical challenges.&lt;/strong&gt; As the team grew with the existing interview format, we noticed more junior-level developers and few seniors. This was perceived to be a problem because most brilliant and self-aware juniors seek growth - and there were few who had 'been there done that' to provide that guidance in our ranks. Also, who were we kidding if we were to claim that we needed these experienced people to know how to detect and reverse palindromes under data type constraints? We wanted to unlock capabilities that an experienced software engineer would expose the team to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The answer for the problem was already online and one candidate had copied most of it.&lt;/strong&gt; This became a problem because the candidates would stumble at the live-programming session after we had already invested a couple or more hours in them. And time wasn't on our side. We needed a methodology that would allow us to invest less time while providing a worthy challenge; and every algorithmic question that could possibly be discussed on coding sites was already out there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One assessment I proposed and implemented (and which a few poor souls were subject to) was done in an attempt to change the strategy from testing to assessing. This came about in the form of a multi-page timed questionnaire that had questions across engineering fields that were applicable to us - stuff like how HTTP works, how backends/frontends interact, some trivia questions on JavaScript and Ruby (our only two languages back then) - with the objective of seeing which fields a candidate scored better in and assessing whether they would &lt;strong&gt;&lt;em&gt;fit our team&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;vis-a-vis&lt;/em&gt; &lt;strong&gt;&lt;em&gt;be good enough&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It was a disaster.&lt;/p&gt;

&lt;p&gt;People read things at different speeds; people felt a need to score 100% in all fields and felt stressed when they couldn't. Candidates on the overall gave us feedback that it was anxiety-inducing. Not the kind of assessment that would prove effective in screening given that we weren't allowing people to demonstrate their best efforts under reasonable stress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Episode III: A Solution Awakens (circa '19)
&lt;/h2&gt;

&lt;p&gt;After multiple other experiments like a room-booking system and a URL shortener (&lt;em&gt;spoiler: both didn't last very long&lt;/em&gt;), we arrived at a palatable technical assessment that wasn't stressful for candidates, wasn't time consuming for us, involved more concepts we use on a daily basis, and which still retained the crux of an important aspect of algorithmic thinking.&lt;/p&gt;

&lt;p&gt;Presenting, &lt;a href="https://gitlab.com/mycf.sg/challenges/dev-checkin"&gt;our TODO-finder&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;What I personally like about this technical challenge is that it solves most - if not all - of the problems that surfaced as we scaled:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The challenge is more similar to work we do on an everyday basis and selects for expertise in areas that's relevant to us. It's more like a &lt;em&gt;fizz-buzz&lt;/em&gt; for developers on a product team.&lt;/li&gt;
&lt;li&gt;More experienced candidates who've forgotten all their CS algorithms would still be able to complete it since it relies on creative expression as much as technical expertise.&lt;/li&gt;
&lt;li&gt;Assessment lies in the how and why, not in the what. This enables a spectrum of correctness over a graded one-dimensional score, removing the pressure to "get it right" and consequently the need to copy a "right" answer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For me, point (3) was especially important because as with most problems we need to solve, there is no "right" answer. It's always about trade-offs and this allows for nice things like discussions about base assumptions made, system design based on past experiences, how/why code was structured in a certain way, why tests were designed this way &lt;em&gt;et cetera&lt;/em&gt;  -  all of which are more useful to know than the thought process behind an algorithm that's probably been covered multiple times on "land that developer job"-type sites.&lt;/p&gt;

&lt;h1&gt;
  
  
  🚦 The Current Interview Process at MCF
&lt;/h1&gt;

&lt;p&gt;Throughout the process of revising the technical assessment, we'd been more-or-less consistent with the end-to-end interview process, and here are the 4 stages of our interview process you'd go through if you're applying to MCF (&lt;em&gt;hint hint&lt;/em&gt;):&lt;/p&gt;

&lt;h2&gt;
  
  
  #1: Courtship
&lt;/h2&gt;

&lt;p&gt;Candidates typically come in through our tribe lead, Steven Koh (he has &lt;a href="https://blog.gds-gov.tech/hire-the-best-tech-talent-in-singapore-part-1-cc37d1dac93d?gi=313c67b872bd"&gt;written a post on his processes&lt;/a&gt;), recommendations from/connections with our colleagues, and sometimes via the HR department.&lt;/p&gt;

&lt;p&gt;Interesting resumes are floated to a Trello board where hiring representatives from the various product teams typically filter based on their current needs. After an applicant is selected for, contact is made by any member of the team who's interested.&lt;/p&gt;

&lt;h2&gt;
  
  
  #2: Technical Chops
&lt;/h2&gt;

&lt;p&gt;For MCF, we use the afore-mentioned technical assessment. The team member initiating contact with the applicant confirms their desire to apply to us, and following which sends the link to them. Upon receiving the response, he/she will float it internally to the team to assess the quality of the assignment. Should it be of sufficient quality, the applicant gets invited to an onsite interview (virtual these days, but you get the idea).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The technical assessment segment differs from team to team depending on contextual and perceived needs so don't be surprised if you're applying to a different product team and the process doesn't seem familiar!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  #3: The Face-to-Face
&lt;/h2&gt;

&lt;p&gt;Ah, the scary one with people.&lt;/p&gt;

&lt;p&gt;Should an applicant reach this stage, we will arrange a 2–3 hour face-to-face interview with 3–4 working-level members of the team that ideally spans from junior to senior. This stage allows us to assess and be assessed by a candidate for culture-fit and primarily serves to introduce the team and its dynamics to them.&lt;/p&gt;

&lt;p&gt;This is followed by a live-programming section where we work with applicants on their solution to the technical assessment. It's pretty much a creative process that involves exploration of the code architecture and design, and working with the candidate to change features/add new ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  #4: The Final Countdown
&lt;/h2&gt;

&lt;p&gt;Should a candidate be passed by the working-level members of the team, a final round is arranged with the hiring manager/HR/product team lead together with 1–2 other working-level members who were not in the initial face-to-face interview. This stage further assesses the candidate's culture fit and tends to introduce the larger organisation, GovTech, to them.&lt;br&gt;
Once a candidate is through, we wait for them to join us about a month later-&lt;/p&gt;




&lt;p&gt;And that concludes the hard process. The subsequent sections focus on the intricacies of the interview and what I've personally experienced and learnt from being part of the interviewers' panel.&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚓️ Interview Objectives
&lt;/h1&gt;

&lt;p&gt;As with any interview, the objective of our interview process is to enable both sides of the table to make an informed decision. The optimisations we can undertake then follows as a (non-)trivial constraint problem: &lt;strong&gt;How can we spend as little time as possible to gain/provide as much data as possible so that we can make an informed decision?&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  🔦 Interview Heuristics
&lt;/h1&gt;

&lt;p&gt;When it comes to the culture-fit chat with candidates, the mental model I've found most constructive considers three aspects of a candidate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Culture&lt;/li&gt;
&lt;li&gt;Capability&lt;/li&gt;
&lt;li&gt;Cause&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While definitely not a strict three-bucket rule, being able to broadly categorise conversations into these aspects has helped me with identifying areas that have/have not been covered so I could influence the conversation to expose data.&lt;/p&gt;

&lt;p&gt;Keeping track of data exposed by either applicant or interviewers also provides me with a way to reason about the &lt;em&gt;good vibes&lt;/em&gt; (or otherwise) that I'm getting about the person in front of me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culture
&lt;/h2&gt;

&lt;p&gt;Culture can be said to be a collective and unspoken &lt;em&gt;modus operandi&lt;/em&gt; in any organisation of humans. In the context of an interview, assessing for culture is similar to assessing a person's personality with the objective of making a best-guess effort at how someone would interact and grow with the existing team and how the team dynamics might change with his/her presence.&lt;/p&gt;

&lt;p&gt;Some examples of questions/statements that expose data related to culture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tell us something you're passionate about.&lt;/li&gt;
&lt;li&gt;What do you do during your free-time?&lt;/li&gt;
&lt;li&gt;How do you think your ex-colleagues/project-mates would describe you?&lt;/li&gt;
&lt;li&gt;A superior comes to you at 6pm telling you she needs something done by that day. How would you decide on what to do? (&lt;em&gt;Editor note: this doesn't happen at MCF, but it wouldn't hurt to know!&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Assuming someone on the team tells you your recent code contributions suck. How would you address the situation? (&lt;em&gt;Editor note: again, just so you know, this doesn't happen at MCF&lt;/em&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Capability
&lt;/h2&gt;

&lt;p&gt;I hear you say, "&lt;em&gt;but we already have a technical assessment?&lt;/em&gt;"  Capability in the context of the non-technical segment of the interview refers to non-technical "&lt;em&gt;special powers&lt;/em&gt;" a candidate might have that cannot be easily expressed on a resume.&lt;/p&gt;

&lt;p&gt;These could be things such as level of self-awareness, clarity of communication, breadth of knowledge, cross-domain areas of knowledge, experience handling difficult situations/people &lt;em&gt;et cetera&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Some examples of questions/statements that expose data related to capability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How would you handle (&lt;em&gt;conjure a socially difficult situation up, like...&lt;/em&gt;) two colleagues who couldn't agree on spaces VS tabs?&lt;/li&gt;
&lt;li&gt;Could you explain the concept of (&lt;em&gt;for example only...&lt;/em&gt;) "Public Key Infrastructure" like we were 5?&lt;/li&gt;
&lt;li&gt;(&lt;em&gt;Compare two things, like...&lt;/em&gt;) JavaScript VS Python, go.&lt;/li&gt;
&lt;li&gt;Assuming you were a (&lt;em&gt;some role, like...&lt;/em&gt;) solution architect, how would you begin designing an e-commerce system that (&lt;em&gt;introduce quirky business contraint, like...&lt;/em&gt;) prides itself on having the "quickest delivery times"?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cause
&lt;/h2&gt;

&lt;p&gt;Cause in a nutshell is best expressed as a question, "what keeps you awake at 3am?". Beyond doing a good job and getting paid well, I think it's important to know why an applicant is doing what they do.&lt;/p&gt;

&lt;p&gt;Knowing why an applicant is in the game enables a better decision to be made by allowing the organisation to get a sensing of the duration a person will stick around in a particular role, what career needs they might have, what challenges we can provide them, and assess how that all ties in with our overall capability needs at that point in time.&lt;/p&gt;

&lt;p&gt;Some examples of questions/statements that expose data related to cause:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What problems do you want to solve?&lt;/li&gt;
&lt;li&gt;Why do you specifically want to join (&lt;em&gt;in our context...&lt;/em&gt;) GovTech instead of other tech companies?&lt;/li&gt;
&lt;li&gt;You're applying to be a (&lt;em&gt;the position, for example...&lt;/em&gt;) DevOps engineer, how do you see yourself growing in this role?&lt;/li&gt;
&lt;li&gt;Imagine it's 2050, what would a typical day in your life be like?&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  💡 Interview Principles
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Fear begets Obscurity
&lt;/h2&gt;

&lt;p&gt;Interviews are anxiety-inducing by their inherent nature. There's a human in front of you who doesn't know anything about you at that point in time. Yet after an hour or two, they will need to decide whether you get to join the party. How obnoxious of them to think they know you!&lt;/p&gt;

&lt;p&gt;Despite the injustice of it all, interviews are a necessary evil and probably the only way of attempting an informed decision on whether or not to bring a person on board. As interviewers, it is up to us to make the best of it with the objective of spending as little time as possible to gain as much data as possible, to make an informed decision.&lt;/p&gt;

&lt;p&gt;Helping a candidate to feel at ease is one way to do so, and the trick lies in getting them to feel safe enough to be themselves.&lt;/p&gt;

&lt;p&gt;Try to recall the last time you felt scared. Even if you're not the type to lie, it's likely that the desire to stay out of trouble would have caused the thought to cross your mind. Fear makes it conducive for people to lie/hide, so don't give the human in front of you a reason to fear.&lt;/p&gt;

&lt;p&gt;In safety, we express more. In safety, we learn more about each other.&lt;/p&gt;




&lt;p&gt;On implementing this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shake the interview process up.&lt;/strong&gt; Anticipation and hope tends to lead to interviewers being imagined as firing squads. When an interview is "like an interview", this affirms the fear. Shake things up by introducing elements to the interview that applicants wouldn't expect and which would take their minds off the session being "an interview". One example I'll always remember is how Steven Koh engaged me in conversation about achieving parallel read-write accesses to relational databases over ping-pong (whut? ikr).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show vulnerability.&lt;/strong&gt; It's normal (even admirable) to want to show your best side to the interviewee. However, I've found that injecting conversations of how the team can fail and has failed (&lt;em&gt;c'mon, no team is perfect&lt;/em&gt;), empowers the applicant and triggers off most engineers' problem-solving nature which take their minds off "the interview".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift the conversation from answers to opinions.&lt;/strong&gt; I've found that phrasing questions in a way that requests for opinions over facts opens people up. Anyway, the point of the interview is to assess the candidate, not their knowledge. There's Google for cold hard knowledge.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Culture &amp;gt; Capability || Cause
&lt;/h2&gt;

&lt;p&gt;I quote a recent message my colleague sent to me while discussing soft skills in engineering which I felt was expressed in a pretty eloquent manner.&lt;/p&gt;

&lt;p&gt;Describing one of his previous professional stints (might sound a little like bragging but I pinky-promise it's not, and that this is what was said),&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"MCF seems to be interested in getting the job done, whether the job is actual delivery feature or sorting out internal processes. XYZ however, feels like people are just trying to please whoever has authority. we have delivery managers accepting any deadline thrown at them without discussing with the team, we have tech leads choosing tech stack because the product owner wanted it. having bad culture doesnt [sic] just make it depressing to work but it actually makes work less effective"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What makes culture so important to get right is that it's based on the collective, perceived and unspoken &lt;em&gt;modus operandi&lt;/em&gt; of the individuals in any group of humans.&lt;/p&gt;

&lt;p&gt;Maintaining a constructive culture is a tough balancing act, with one unsavoury apple potentially spoiling the lot (whatever 'spoil' means in your context) either through direct or indirect interventions.&lt;/p&gt;

&lt;p&gt;As an interviewer, I've found it's also important to note that what works for one team may not work for another and it's crucial that interviewers assess this before the interview based on their own context. The afore-mentioned description of the other team didn't work for my colleague, but it might have worked for others who enjoy the pressure/prefer to leave the decision of technologies to others.&lt;/p&gt;

&lt;p&gt;There is no right or wrong in culture, only constructive to the objective, or otherwise.&lt;/p&gt;




&lt;p&gt;On implementing this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start conversation topics with culture-category questions.&lt;/strong&gt; Capabilities and cause can be exposed through continuing the conversation on how they achieved it and why they decided to pursue it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get them to talk about themselves.&lt;/strong&gt; Assessing a person's personality is only possible when they feel safe to be who they truly are over who they are in interviews, and what better way to do that then getting candidates to talk about what they know best - themselves?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respond with kindness and acceptance.&lt;/strong&gt; A continuation from the previous point: our response as interviewers will decide whether or not a candidate feels comfortable with us. In anyways, we shouldn't be judging people without living their lives.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Assess not Test
&lt;/h2&gt;

&lt;p&gt;We begin with the distinction: the output of a test is a measurement; the output of an assessment is data.&lt;/p&gt;

&lt;p&gt;Testing by its very nature of desiring a result/measurement, assumes that there exists a "best" (or "only") way resulting in applicants being graded on a single scale that "is ideal". But the truth is in a constantly evolving environment such as the one we are in today, no one truly knows a "best" or "only" or "ideal" way. Not even leaders of organisations.&lt;/p&gt;

&lt;p&gt;Assessing on the other hand comes with the premise that the interviewer acknowledges that there exists variations and different dimensions that require interpretation within a larger context and objective. This leads to a process that exposes and collects information without interpretation which I argue is more constructive than processes derived with &lt;em&gt;testing&lt;/em&gt; in mind.&lt;/p&gt;

&lt;p&gt;While it's always easier to test rather than assess, it's much more constructive in interviews to assess; and especially so in our context of hiring for a product team that in my opinion requires a skilfully crafted level of diversity in terms of opinions and technical expertise in order to excel.&lt;/p&gt;




&lt;p&gt;On implementing this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be child-like curious.&lt;/strong&gt; Changing our mindset to one that's curious opens up the possibility for collaborative exploration which exposes data that will remain hidden if we choose to affirm instead (for example by asking something, and then going &lt;em&gt;"right?"&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know your team.&lt;/strong&gt; In order to be able to interpret data collected, we need to know what we're looking for. What aspects of culture/capability/cause seems to be lacking in the team? This hints at the types of questions we should be asking to gain more data in specific aspects and this also means that especially so in teams whose value lies in their collective technical expertise, the HR department should try to avoid being the gatekeeper as far as company policy allows for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  🧸 Improving Skills Related to Interviewing
&lt;/h1&gt;

&lt;p&gt;So you're an engineer reading this and your forward-thinking HR department has tasked you to interview an applicant. Here are some ways that I found I could improve on myself during interviews:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask better questions.&lt;/strong&gt; An answer cannot exist without a question, so it follows that the quality of your questions will impact the quality of your answers. Here's a nice piece on asking questions that I came across recently  - &lt;a href="https://marker.medium.com/7-ways-leaders-can-ask-better-questions-e26d3b2c0b73"&gt; 7 ways leaders can ask better questions&lt;/a&gt;. Some easy rules that I've found to work in most contexts would be to choose to ask "how" and "why" over "what"/"who"/"when".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving somatic expression.&lt;/strong&gt; Communication is only 7% verbal and 93% non-verbal. How safe someone feels to express themselves has much less to do with what was said, and more to do with how it was said. How do people perceive your accent? What does your body language say about you? Are your body language, emotions, and use of language congruent? In the area of somatics, there's no one dimensional scale that enables us to "become better". Becoming "better" at our somatic expression is &lt;strong&gt;a matter of building range&lt;/strong&gt;. &lt;em&gt;Can I appear confident if it's constructive to be? Can I appear loving if it's constructive to be?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letting go.&lt;/strong&gt; It's normal to want to feel in control of your environment. In interviews however, I've found that trying to maintain my control over my environment, resulted in me depriving control from the candidate. This often leads to a conversation that reaffirms my beliefs rather than enabling the candidate to express theirs. This tended to deprive me of data that would've been pivotal. Similar to the previous point on somatics, letting go is about knowing yourself. What emotions are in play – for you – when you desire to be in control of the environment. One excellent book I've read on this is &lt;a href="https://www.goodreads.com/book/show/27209485-emotional-agility"&gt;Emotional Agility by Susan David&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  🌈 A Conclusion Because I Heard People Like Structure
&lt;/h1&gt;

&lt;p&gt;Conducting an awesome technical interview involves much more than just 'having a chat with' or 'testing' an applicant. When done well, the technical interview should expose enough data for both the applicant as well as the organisation to know their answer.&lt;/p&gt;

&lt;p&gt;Having a clear definite "yes" or "no" should ultimately be the objective, and an interview that enables either party to be completely sure of the other, is really what defines a good interview.&lt;/p&gt;

&lt;h1&gt;
  
  
  🔌 THAT Shameless Plug
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Interested to do #techforpublicgood?
&lt;/h2&gt;

&lt;p&gt;We've been expanding as an organisation and there never seems to be enough people.&lt;/p&gt;

&lt;p&gt;If you're gifted/talented/passionate about software development and wish to apply yourself to change how technology is being delivered in the public sector for public good, feel free to hit me up at &lt;a href="//mailto:joseph_goh@tech.gov.sg"&gt;joseph_goh@tech.gov.sg&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;p&gt;Shoutout to the partner-in-crime, Ryan Goh, and (more recently) Sze Ying,  Dickson Tan, and Wynn, for walking this journey with me and driving me to improve how things are with our technical assessments.&lt;/p&gt;

&lt;p&gt;Also, thanks to Steven Koh who's provided sagely guidance over the years to the ACE Hiring (not firing) Squad.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;P.S. Thank you to Sze Ying, Dickson, and Wynn for being unpaid editors&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>New Features on 19th March</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Fri, 20 Mar 2020 06:33:55 +0000</pubDate>
      <link>https://dev.to/mcf/new-features-on-19th-march-3l9k</link>
      <guid>https://dev.to/mcf/new-features-on-19th-march-3l9k</guid>
      <description>&lt;p&gt;We've just completed Sprint 75 and here's the start of many posts that will document shiny new stuff from our weekly deployments (hopefully these can become more frequent, but policies are policies!):&lt;/p&gt;




&lt;p&gt;Here's what's new this time:&lt;/p&gt;

&lt;h1&gt;
  
  
  Features
&lt;/h1&gt;

&lt;h2&gt;
  
  
  New section header in resume analysis
&lt;/h2&gt;

&lt;p&gt;If you've been using our new resume analyzer feature (powered by &lt;a href="https://www.jobscan.co/"&gt;JobScan&lt;/a&gt;), you'd notice that we have a new section to provide work experience recommendations for your uploaded resumes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tv_2lcB6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0remjmacfg1zf1ntuu11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tv_2lcB6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0remjmacfg1zf1ntuu11.png" alt="Check Resume modal showing Experience section header"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you haven't been, &lt;a href="https://mycareersfuture.sg"&gt;why haven't you?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Updated UI for nudges
&lt;/h2&gt;

&lt;p&gt;Our sister team had also worked on some of the digital nudges that we've had from a few deployments ago, adding a &lt;strong&gt;Learn More&lt;/strong&gt; button and making it arguably more user-friendly than before by having both a mobile and desktop version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ezbjzRt_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ma6dr8kxtf44aw4jgi6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ezbjzRt_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ma6dr8kxtf44aw4jgi6r.png" alt="Mobile view of nudges"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LOBLpIm---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kxfj3n8d3yi2fypd0o6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LOBLpIm---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kxfj3n8d3yi2fypd0o6c.png" alt="Desktop view of nudges"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Bugs Fixed
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;When all skills in the skills box are deleted, the nudge box no longer disappears.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Till next time! Got feedback/flames/feature requests? Drop us a note in the comments section and we'll get back to you-&lt;/p&gt;

</description>
      <category>release</category>
      <category>sprint75</category>
    </item>
    <item>
      <title>Hello Job.</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Tue, 17 Mar 2020 05:53:20 +0000</pubDate>
      <link>https://dev.to/mcf/hello-job-410m</link>
      <guid>https://dev.to/mcf/hello-job-410m</guid>
      <description>&lt;p&gt;You've probably &lt;a href="https://www.straitstimes.com/business/economy/better-matching-more-listings-on-government-job-portal"&gt;seen us in the news&lt;/a&gt;, read about us &lt;a href=""&gt;from other places&lt;/a&gt;, or even &lt;a href="https://www.wsg.gov.sg/content/dam/ssg-wsg/wsg/ag-tg/jobseekers/Factsheet_on_MyCareersFuture.sg.pdf"&gt;the factsheet if that's your kinda thing&lt;/a&gt;. Now here's what goes on behind the scenes.&lt;/p&gt;

&lt;p&gt;If you hadn't found the willpower to click any of the links above... (that's dissapointing, but,) We're the technical team behind the MyCareersFuture platform. MyCareersFuture is a jobs portal product created in collaboration between Workforce Singapore and GovTech to support the careers of Singapore residents.&lt;/p&gt;

&lt;p&gt;Our initial focus was on Professionals, Managers, Executives, and Technicians (PMETs) in later stages of their careers, and we're gradually widening that focus to support any resident in any stage of their careers.&lt;/p&gt;

&lt;p&gt;It's often easy to see the glamourous side of things as the media tends to like to portray it (sponsored or otherwise), but behind every awesome product - not saying we're there yet, but we will be - is a team in the depths of the engine room that truly believes in the product and who are often very invisible.&lt;/p&gt;

&lt;p&gt;This blog was created to tell that story (and more), so if tech's your kinda thing, MyCareersFuture happens to be built in-house, and you should (please?) follow us if you'd like to get the latest from real humans on the ground.&lt;/p&gt;

&lt;p&gt;If you're interested, other pieces by the technical team can be found at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.tech.gov.sg/media/technews/hire-calling-making-mycareersfuture-inclusive"&gt;A hire calling: Making MyCareersFuture an inclusive platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.gds-gov.tech/mycareersfuture-as-told-by-the-technical-team-3ae36802c973"&gt;MyCareersFuture: As Told By The Technical Team&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See you in a post soon!&lt;/p&gt;

&lt;p&gt;P.S. We're also hiring, if you consider yourself talented enough, discounting any Dunning-Kruger effects you're aware of, and would like to join us/GovTech in general, you may hit me up at &lt;a href="mailto:joseph_goh@tech.gov.sg"&gt;joseph_goh@tech.gov.sg&lt;/a&gt; 😊&lt;/p&gt;

</description>
      <category>mycareersftuture</category>
      <category>mcf</category>
      <category>init</category>
      <category>introduction</category>
    </item>
    <item>
      <title>Hello from the other side</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Sun, 09 Feb 2020 05:21:03 +0000</pubDate>
      <link>https://dev.to/zephinzer/hello-from-the-other-side-260o</link>
      <guid>https://dev.to/zephinzer/hello-from-the-other-side-260o</guid>
      <description>&lt;p&gt;I began writing technical pieces more seriously about four years ago. Medium was on the rise and I joined a company that encouraged writing technical pieces, and it began - the journey towards &lt;strong&gt;not writing&lt;/strong&gt;. Why?&lt;/p&gt;

&lt;p&gt;So why did I slow down/stop? The pressure.&lt;/p&gt;

&lt;p&gt;The pressure of having ~500 followers on Medium and the self-imposed expectation of continuing to write awesome articles. I began to see this as being a geek-likewhore of sorts and I began to resent it. Starting a new article usually began with joy and a desire to share some newfound insight, to provide an alternative point-of-view. This quickly turned into self-doubt (common thoughts: &lt;em&gt;"anyone who put in enough thought would be able to discover this for themselves too"&lt;/em&gt;, &lt;em&gt;"somebody has already written on this, am I wasting my time?"&lt;/em&gt;, &lt;em&gt;"it's never going to outdo your most famous pieces"&lt;/em&gt;), and then into fear (&lt;em&gt;"what if this story sucked?"&lt;/em&gt;, &lt;em&gt;"what if I can't hit the same applause as my previous articles?"&lt;/em&gt;), effectively bringing my writing to a halt.&lt;/p&gt;

&lt;p&gt;To be honest, I haven't exactly found a remedy to all that self-doubt, but I do get feedback now and then on having helped people to understand certain concepts which is heartening and makes me want to write again. So I'm going to do just that here on &lt;code&gt;dev.to&lt;/code&gt;. No followers, no likes, I'll start again from fresh, writing a little each day on my thoughts and reflections about technology creation and stuff related to it.&lt;/p&gt;

&lt;p&gt;If you're reading till this far, thanks for reading!&lt;/p&gt;

</description>
      <category>personal</category>
      <category>thoughts</category>
      <category>reflection</category>
    </item>
    <item>
      <title>A Kubectl Alias Dotfile &amp; Other Life-Enhancing Kubectl Tools</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Sun, 23 Jun 2019 08:59:43 +0000</pubDate>
      <link>https://dev.to/zephinzer/a-kubectl-alias-dotfile-other-life-enhancing-kubectl-tools-1dn0</link>
      <guid>https://dev.to/zephinzer/a-kubectl-alias-dotfile-other-life-enhancing-kubectl-tools-1dn0</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;It's here: &lt;a href="https://gist.github.com/zephinzer/f0a321e9bcb204debd45160b890eb6a3"&gt;https://gist.github.com/zephinzer/f0a321e9bcb204debd45160b890eb6a3&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  The backstory
&lt;/h1&gt;

&lt;p&gt;I began actively using Kubernetes some time back, and the commands were getting too lengthy to type out. Imagine having a pod on fire and you're still typing &lt;code&gt;kubectl get pods -n somenamespace | grep deploymentname&lt;/code&gt;, finding the pod with the dreaded &lt;code&gt;CrashLoopBackOff&lt;/code&gt;, and finally running &lt;code&gt;kubectl log -f -n somenamespace podname&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So. I created a set of aliases that worked using abbreviations of Kubernetes's inbuilt verbs and resource types:&lt;/p&gt;

&lt;h2&gt;
  
  
  Verbs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;apply (a)&lt;/li&gt;
&lt;li&gt;config (conf)&lt;/li&gt;
&lt;li&gt;describe (d)&lt;/li&gt;
&lt;li&gt;delete (del)&lt;/li&gt;
&lt;li&gt;edit (e)&lt;/li&gt;
&lt;li&gt;exec (exec)&lt;/li&gt;
&lt;li&gt;expose (exp)&lt;/li&gt;
&lt;li&gt;get (g)&lt;/li&gt;
&lt;li&gt;logs (l)&lt;/li&gt;
&lt;li&gt;port-forward (pf)&lt;/li&gt;
&lt;li&gt;run (r)&lt;/li&gt;
&lt;li&gt;top (t)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;config (c)&lt;/li&gt;
&lt;li&gt;configmap (cm)&lt;/li&gt;
&lt;li&gt;cronjob (cj)&lt;/li&gt;
&lt;li&gt;clusterrole (cr)&lt;/li&gt;
&lt;li&gt;clusterrolebinding (crb)&lt;/li&gt;
&lt;li&gt;deployment (d)&lt;/li&gt;
&lt;li&gt;daemonset (ds)&lt;/li&gt;
&lt;li&gt;endpoint (e)&lt;/li&gt;
&lt;li&gt;ingress (i)&lt;/li&gt;
&lt;li&gt;job (j)&lt;/li&gt;
&lt;li&gt;pod (p)&lt;/li&gt;
&lt;li&gt;persistentvolume (pv)&lt;/li&gt;
&lt;li&gt;persistentvolumeclaim (pvc)&lt;/li&gt;
&lt;li&gt;node (n)&lt;/li&gt;
&lt;li&gt;namespace (ns)&lt;/li&gt;
&lt;li&gt;role (r)&lt;/li&gt;
&lt;li&gt;rolebinding (rb)&lt;/li&gt;
&lt;li&gt;secret (sec)&lt;/li&gt;
&lt;li&gt;service (s)&lt;/li&gt;
&lt;li&gt;serviceaccount (sa)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lazy Utility Exports
&lt;/h2&gt;

&lt;p&gt;This set of aliases also exports two functions I've found really useful to avoid typing the &lt;code&gt;-n &amp;lt;NAMESPACE&amp;gt;&lt;/code&gt; which happens quite often if you've correctly set up your cluster. Presenting..&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kcsetns ${YOUR_NAMESPACE}&lt;/code&gt; - this sets your namespace to a string value which all future &lt;code&gt;kubectl&lt;/code&gt; commands will respect.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kcgetns&lt;/code&gt; - this retrieves the current namespace context of your &lt;code&gt;kubectl&lt;/code&gt; commands.&lt;/p&gt;




&lt;p&gt;OK, so I promised some other life-enhancing tools as well. I've found these extremely useful when administering a Kubenretes cluster without a UI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/derailed/k9s"&gt;derailed/k9s&lt;/a&gt; - a really cool curses-based interface that imo works better than the Kubernetes Dashboard for administering a cluster&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/wercker/stern"&gt;wercker/stern&lt;/a&gt; - a logs collater that allows you to view logs from all selected pods at once&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Till next time!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>kubectl</category>
      <category>dotfile</category>
    </item>
    <item>
      <title>First thoughts on Apollo GraphQL</title>
      <dc:creator>Joseph Matthias Goh</dc:creator>
      <pubDate>Tue, 15 Jan 2019 14:35:20 +0000</pubDate>
      <link>https://dev.to/zephinzer/first-thoughts-on-apollo-graphql-3gi9</link>
      <guid>https://dev.to/zephinzer/first-thoughts-on-apollo-graphql-3gi9</guid>
      <description>&lt;h2&gt;
  
  
  It Begins
&lt;/h2&gt;

&lt;p&gt;While I've been working on more operations related technologies, my team at work has been busy with pushing forward in a new service using Apollo GraphQL. So I decided to take a look at what the hype's all about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up
&lt;/h2&gt;

&lt;p&gt;Setting up was pretty seamless. Install the dependencies and it's good to go. I followed &lt;a href="https://www.apollographql.com/docs/apollo-server/getting-started.html"&gt;this guide from the official docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apollo GraphQL includes a handy GraphQL Playground which seems better than the GraphiQL which was the way to go when I previously touched GraphQL.&lt;/p&gt;

&lt;p&gt;To get started, one simply runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i apollo-graphql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And then in the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;staticData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,},{&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  type Data {
    a: Number
    b: Number
  }
  type Query {
    data: [Data]
  }
`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;staticData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;As an ops engineer, the first question I usually ask is &lt;em&gt;"how do I monitor this thing?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The official Apollo GraphQL documentation recommends the Apollo Engine, but that requires an API key, and it also meant that I would likely have different sources that I'd need to monitor when it comes to assessing the performance of the service. I'm looking for things such as response time, remote IP address, size of payload, request body structure &lt;em&gt;et cetera&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It would be nice if there could be some way to use Prometheus with this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Use the Context
&lt;/h3&gt;

&lt;p&gt;So I explored a little and found the &lt;code&gt;context&lt;/code&gt; option which could be passed into the &lt;code&gt;ApolloServer&lt;/code&gt; constructor. It exposes a function that exposes the properties from both the incoming request and outgoing response. Could the context be used to inject a Prometheus metrics listener?&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Use the ApolloServer as a middleware
&lt;/h3&gt;

&lt;p&gt;One nice thing about Apollo GraphQL is that it can be used as a middleware. Changing the dependency to &lt;code&gt;apollo-graphql-express&lt;/code&gt; allowd me to use an Express server with &lt;code&gt;express-prom-bundle&lt;/code&gt; to monitor the requests coming in. However, there's a problem: All paths are &lt;code&gt;/graphql&lt;/code&gt; which doesn't really make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Structuring
&lt;/h2&gt;

&lt;p&gt;With the basic example, it's easy to see how the string passed to the &lt;code&gt;gql&lt;/code&gt; function could be extended modularly.&lt;/p&gt;

&lt;p&gt;Given that we have a directory structure that mimics our data model, each of these models could export a GraphQL type definition which can then be merged into a master schema of sorts. Took reference from &lt;a href="https://blog.apollographql.com/modularizing-your-graphql-schema-code-d7f71d5ed5f2"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;GraphQL still seems pretty intriguing to me. According to what I've learnt about it so far, Apollo GraphQL also offers React Apollo GraphQL which seems to make writing queries easier and removes the need for using Redux, which is the real benefit I'm looking for.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ability to define response schemas is pretty interesting to me and allows us to easily test compatibility with other consuming/providing services with contract driven testing.&lt;/li&gt;
&lt;li&gt;Despite the benefit, how can we monitor it? A quick Google search doesn't turn up much on monitoring GraphQL queries. I'd rather have an unoptimised service that can be monitored that's written RESTfully, over an optimised one using GraphQL that cannot.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>graphql</category>
      <category>apollo</category>
      <category>express</category>
      <category>node</category>
    </item>
  </channel>
</rss>
