Practical Guide to SRE: Incident Severity Levels

In companies with constant growth and large scale, incidents are inevitable. A robust incident management strategy eventually leads to better management and handling of issues. It is an essential part of reliability engineering, ensuring that a team is prepared to manage incidents. It can prevent the loss of millions of dollars in revenue in times of breaches or downtimes. Good incident management improves customer experience and empowers engineering teams to meet their uptime goals. 

In this blog, we will discuss how companies can identify and prioritize incidents for faster resolution. Incidents could vary a lot on the surface level, and hence it is essential to classify them according to specific, well-defined parameters. All incidents are not equal. For instance, a system outage during peak hours is much more stressful than handling when most customers are asleep. 

Getting Started With Static Code Analysis

Static code analysis is the practice of examining application’s source, bytecode, or binary code without ever executing the program code itself. Instead, the code under review is analyzed to identify any defects, flaws, or vulnerabilities which may compromise the integrity or security of the application itself. In this Refcard, we explore the necessary components and steps for getting started with static code analysis, including CI/CD integrations, OWASP Benchmark, and more.

Develop a Scalable Event Listener for Blockchain

Developing a Scalable Event Listener for Hyperledger Fabric

Using an event listener for replicating data stored in Hyperledger Fabric ledgers is an emerging pattern in todays’ enterprise solutions based on Hyperledger Fabric oriented distributed ledger technology-based platform. This article will provide a comprehensive guide for building an event listener using Hyperledger fabric-sdk-go. All the examples have assumed the usage of Hyperledger Fabric v2.2.x platform.

Why We Need Event Listeners

For many blockchain-based applications, it has been observed that there is a requirement of storing all or some of the assets into an off-chain data storage. There could be several motivations for such requirements. Some of the frequently encountered cases are

Let’s Use Optional to Fix Our Method Contract

A method is a contract; when we define one, we put our thoughts into it. We specify the parameters with their type and also a return type. When we invoke a method, we expect it to behave according to the contract. If it doesn’t, it’s a violation of the contract.

We deal with such a violation all the time. We invoke a method with its proper arguments, and we get a return. However, sometimes we end up getting a null, which is, in fact, a clear violation. We shouldn’t accept such a violation. If a method cannot return the value of the specified type, it should mention that in the method signature, the method may or may not be returning the value you are expecting. If we know it from the method signature, we then write our code accordingly.

IoT Data Management: Why You Need It and What Are Its Ultimate Challenges?

As a top contributor to the massive volumes of data created across the globe, IoT needs leaner approaches to data management and enhanced sensibility towards data governance. Today’s unprecedented levels of heterogeneity, volume, and connectivity call for data management strategies that consider scale, data gravity, and integration. 

Why IoT Data Management?

What drives the need for data management within an organization? Below are some answers:

Win $10,000 in the IBM Build-A-Bot Challenge: Automation for Good

IBM is holding a challenge and wants to know how you can use Automation for Good!

The Details

The IBM Build-A-Bot Challenge is open from June 15 to July 15, 2021 at Midnight ET. There are two prizes of $10,000 each, one for the Best IBM Robotic Process Automation Solution that Improves Your Workplace and the other for the Best IBM Robotic Process Automation Solution that Solves a Social Challenge. You can enter one submission in each category. You can work alone or in teams of up to five people.

One gallon of Milk and a medium sized Cloud please

My ISP is Marco, and I don't mean Marco as in Ltd or Inc - No, I mean Marco my neighbour. He lives 200 meters away from me, and serves half the village I live in with internet. A long time ago before traditional ISP companies even bothered to offer internet in the village, Marco installed antennas around the entire village, offered internet services to all who wanted it, and is today the largest ISP in my village. Today of course we've got several huge ISPs in Cyprus, but nobody really care. We've got Marco, and we're happy with using his internet. He was there for us when nobody else cared, so why would we want to switch to some huge giga corporation today?

When the big banks stopped allowing people to pay their bills over the counter, Marco stepped in, and is now paying people's bills too. Need your bills paid? Drop by at Marco's internet store, leave enough cash to cover your bill, add 2 EURO commission on top, and your bills are paid within a day or two. Needless to say, but for the older people in my village, such an offer is precious, since most of the really old people in my village don't even know how to pay their bills online.

Creating A Multi-Author Blog With Next.js

In this article, we are going to build a blog with Next.js that supports two or more authors. We will attribute each post to an author and show their name and picture with their posts. Each author also gets a profile page, which lists all posts they contributed. It will look something like this:

We are going to keep all information in files on the local filesystem. The two types of content, posts and authors, will use different types of files. The text-heavy posts will use Markdown, allowing for an easier editing process. Because the information on authors is lighter, we will keep that in JSON files. Helper functions will make reading different file types and combining their content easier.

Next.js lets us read data from different sources and of different types effortlessly. Thanks to its dynamic routing and next/link, we can quickly build and navigate to our site’s various pages. We also get image optimization for free with the next/image package.

By picking the “batteries included” Next.js, we can focus on our application itself. We don’t have to spend any time on the repetitive groundwork new projects often come with. Instead of building everything by hand, we can rely on the tested and proven framework. The large and active community behind Next.js makes it easy to get help if we run into issues along the way.

After reading this article, you will be able to add many kinds of content to a single Next.js project. You will also be able to create relationships between them. That allows you to link things like authors and posts, courses and lessons, or actors and movies.

This article assumes basic familiarity with Next.js. If you have not used it before, you might want to read up on how it handles pages and fetches data for them first.

We won’t cover styling in this article and focus on making it all work instead. You can get the result on GitHub. There is also a stylesheet you can drop into your project if you want to follow along with this article. To get the same frame, including the navigation, replace your pages/_app.js with this file.

Setup

We begin by setting up a new project using create-next-app and changing to its directory:

$ npx create-next-app multiauthor-blog
$ cd multiauthor-blog

We will need to read Markdown files later. To make this easier, we also add a few more dependencies before getting started.

multiauthor-blog$ yarn add gray-matter remark remark-html

Once the installation is complete, we can run the dev script to start our project:

multiauthor-blog$ yarn dev

We can now explore our site. In your browser, open http://localhost:3000. You should see the default page added by create-next-app.

In a bit, we’ll need a navigation to reach our pages. We can add them in pages/_app.js even before the pages exist.

import Link from 'next/link'

import '../styles/globals.css'

export default function App({ Component, pageProps }) {
  return (
    <>
      <header>
        <nav>
          <ul>
            <li>
              <Link href="/">
                <a>Home</a>
              </Link>
            </li>

            <li>
              <Link href="/posts">
                <a>Posts</a>
              </Link>
            </li>

            <li>
              <Link href="/authors">
                <a>Authors</a>
              </Link>
            </li>
          </ul>
        </nav>
      </header>

      <main>
        <Component {...pageProps} />
      </main>
    </>
  )
}

Throughout this article, we’ll add these missing pages the navigation points to. Let’s first add some posts so we have something to work with on a blog overview page.

Creating Posts

To keep our content separate from the code, we’ll put our posts in a directory called _posts/. To make writing and editing easier, we’ll create each post as a Markdown file. Each post’s filename will serve as the slug in our routes later. The file _posts/hello-world.md will be accessible under /posts/hello-world, for example.

Some information, like the full title and a short excerpt, goes in the frontmatter at the beginning of the file.

---
title: "Hello World!"
excerpt: "This is my first blog post."
createdAt: "2021-05-03"
---
Hey, how are you doing? Welcome to my blog. In this post, …

Add a few more files like this so the blog doesn’t start out empty:

multi-author-blog/
├─ _posts/
│  ├─ hello-world.md
│  ├─ multi-author-blog-in-nextjs.md
│  ├─ styling-react-with-tailwind.md
│  └─ ten-hidden-gems-in-javascript.md
└─ pages/
   └─ …
 

You can add your own or grab these sample posts from the GitHub repository.

Listing All Posts

Now that we have a few posts, we need a way to get them onto our blog. Let’s start by adding a page that lists them all, serving as the index of our blog.

In Next.js, a file created under pages/posts/index.js will be accessible as /posts on our site. The file must export a function that will serve as that page’s body. Its first version looks something like this:

export default function Posts() {
  return (
    <div className="posts">
      <h1>Posts</h1>

      {/* TODO: render posts */}
    </div>
  )
}

We don’t get very far because we don’t have a way to read the Markdown files yet. We can already navigate to http://localhost:3000/posts, but we only see the heading.

We now need a way to get our posts on there. Next.js uses a function called getStaticProps() to pass data to a page component. The function passes the props in the returned object to the component as props.

From getStaticProps(), we are going to pass the posts to the component as a prop called posts. We’ll hardcode two placeholder posts in this first step. By starting this way, we define what format we later want to receive the real posts in. If a helper function returns them in this format, we can switch over to it without changing the component.

The post overview won’t show the full text of the posts. For this page, the title, excerpt, permalink, and date of each post are enough.

 export default function Posts() { … }

+export function getStaticProps() {
+  return {
+    props: {
+      posts: [
+        {
+          title: "My first post",
+          createdAt: "2021-05-01",
+          excerpt: "A short excerpt summarizing the post.",
+          permalink: "/posts/my-first-post",
+          slug: "my-first-post",
+        }, {
+          title: "My second post",
+          createdAt: "2021-05-04",
+          excerpt: "Another summary that is short.",
+          permalink: "/posts/my-second-post",
+          slug: "my-second-post",
+        }
+      ]
+    }
+  }
+}

To check the connection, we can grab the posts from the props and show them in the Posts component. We’ll include the title, date of creation, excerpt, and a link to the post. For now, that link won’t lead anywhere yet.

+import Link from 'next/link'

-export default function Posts() {
+export default function Posts({ posts }) {
   return (
     <div className="posts">
       <h1>Posts</h1>

-      {/ TODO: render posts /}
+      {posts.map(post => {
+        const prettyDate = new Date(post.createdAt).toLocaleString('en-US', {
+          month: 'short',
+          day: '2-digit',
+          year: 'numeric',
+        })
+
+        return (
+          <article key={post.slug}>
+            <h2>
+              <Link href={post.permalink}>
+                <a>{post.title}</a>
+              </Link>
+            </h2>
+
+            <time dateTime={post.createdAt}>{prettyDate}</time>
+
+            <p>{post.excerpt}</p>
+
+            <Link href={post.permalink}>
+              <a>Read more →</a>
+            </Link>
+          </article>
+        )
+      })}
     </div>
   )
 }

 export function getStaticProps() { … }

After reloading the page in the browser, it now shows these two posts:

We don’t want to hardcode all our blog posts in getStaticProps() forever. After all, that is why we created all these files in the _posts/ directory earlier. We now need a way to read those files and pass their content to the page component.

There are a few ways we could do that. We could read the files right in getStaticProps(). Because this function runs on the server and not the client, we have access to native Node.js modules like fs in it. We could read, transform, and even manipulate local files in the same file we keep the page component.

To keep the file short and focused on one task, we’re going to move that functionality to a separate file instead. That way, the Posts component only needs to display the data, without also having to read that data itself. This adds some separation and organization to our project.

By convention, we are going to put functions reading data in a file called lib/api.js. That file will hold all functions that grab our content for the components that display it.

For the posts overview page, we want a function that reads, processes, and returns all posts. We’ll call it getAllPosts(). In it, we first use path.join() to build the path to the _posts/ directory. We then use fs.readdirSync() to read that directory, which gives us the names of all files in it. Mapping over these names, we then read each file in turn.

import fs from 'fs'
import path from 'path'

export function getAllPosts() {
  const postsDirectory = path.join(process.cwd(), '_posts')
  const filenames = fs.readdirSync(postsDirectory)

  return filenames.map(filename => {
    const file = fs.readFileSync(path.join(process.cwd(), '_posts', filename), 'utf8')

    // TODO: transform and return file
  })
}

After reading the file, we get its contents as a long string. To separate the frontmatter from the text of the post, we run that string through gray-matter. We’re also going to grab each post’s slug by removing the .md from the end of its filename. We need that slug to build the URL from which the post will be accessible later. Since we don’t need the Markdown body of the posts for this function, we can ignore the remaining content.

import fs from 'fs'
 import path from 'path'
+import matter from 'gray-matter'

 export function getAllPosts() {
   const postsDirectory = path.join(process.cwd(), '_posts')
   const filenames = fs.readdirSync(postsDirectory)

   return filenames.map(filename => {
     const file = fs.readFileSync(path.join(process.cwd(), '_posts', filename), 'utf8')

-    // TODO: transform and return file
+    // get frontmatter
+    const { data } = matter(file)
+
+    // get slug from filename
+    const slug = filename.replace(/.md$/, '')
+
+    // return combined frontmatter and slug; build permalink
+    return {
+      ...data,
+      slug,
+      permalink: /posts/${slug},
+    }
   })
 }

Note how we spread ...data into the returned object here. That lets us access values from its frontmatter as {post.title} instead of {post.data.title} later.

Back in our posts overview page, we can now replace the placeholder posts with this new function.

+import { getAllPosts } from '../../lib/api'

 export default function Posts({ posts }) { … }

 export function getStaticProps() {
   return {
     props: {
-      posts: [
-        {
-          title: "My first post",
-          createdAt: "2021-05-01",
-          excerpt: "A short excerpt summarizing the post.",
-          permalink: "/posts/my-first-post",
-          slug: "my-first-post",
-        }, {
-          title: "My second post",
-          createdAt: "2021-05-04",
-          excerpt: "Another summary that is short.",
-          permalink: "/posts/my-second-post",
-          slug: "my-second-post",
-        }
-      ]
+      posts: getAllPosts(),
     }
   }
 }

After reloading the browser, we now see our real posts instead of the placeholders we had before.

Adding Individual Post Pages

The links we added to each post don’t lead anywhere yet. There is no page that responds to URLs like /posts/hello-world yet. With dynamic routing, we can add a page that matches all paths like this.

A file created as pages/posts/[slug].js will match all URLs that look like /posts/abc. The value that appears instead of [slug] in the URL will be available to the page as a query parameter. We can use that in the corresponding page’s getStaticProps() as params.slug to call a helper function.

As a counterpart to getAllPosts(), we’ll call that helper function getPostBySlug(slug). Instead of all posts, it will return a single post that matches the slug we pass it. On a post’s page, we also need to show the underlying file’s Markdown content.

The page for individual posts looks like the one for the post overview. Instead of passing posts to the page in getStaticProps(), we only pass a single post. Let’s do the general setup first before we look at how to transform the post’s Markdown body to usable HTML. We’re going to skip the placeholder post here, using the helper function we’ll add in the next step immediately.

import { getPostBySlug } from '../../lib/api'

export default function Post({ post }) {
  const prettyDate = new Date(post.createdAt).toLocaleString('en-US', {
    month: 'short',
    day: '2-digit',
    year: 'numeric',
  })

  return (
    <div className="post">
      <h1>{post.title}</h1>

      <time dateTime={post.createdAt}>{prettyDate}</time>

      {/ TODO: render body /}
    </div>
  )
}

export function getStaticProps({ params }) {
  return {
    props: {
      post: getPostBySlug(params.slug),
    },
  }
}

We now have to add the function getPostBySlug(slug) to our helper file lib/api.js. It is like getAllPosts(), with a few notable differences. Because we can get the post’s filename from the slug, we don’t need to read the entire directory first. If the slug is 'hello-world', we are going to read a file called _posts/hello-world.md. If that file doesn’t exist, Next.js will show a 404 error page.

Another difference to getAllPosts() is that this time, we also need to read the post’s Markdown content. We can return it as render-ready HTML instead of raw Markdown by processing it with remark first.

 import fs from 'fs'
 import path from 'path'
 import matter from 'gray-matter'
+import remark from 'remark'
+import html from 'remark-html'

 export function getAllPosts() { … }

+export function getPostBySlug(slug) {
+  const file = fs.readFileSync(path.join(process.cwd(), '_posts', ${slug}.md), 'utf8')
+
+  const {
+    content,
+    data,
+  } = matter(file)
+
+  const body = remark().use(html).processSync(content).toString()
+
+  return {
+    ...data,
+    body,
+  }
+}

In theory, we could use the function getAllPosts() inside getPostBySlug(slug). We’d first get all posts with it, which we could then search for one that matches the given slug. That would mean we would always need to read all posts before we could get a single one, which is unnecessary work. getAllPosts() also doesn’t return the posts’ Markdown content. We could update it to do that, in which case it would do more work than it currently needs to.

Because the two helper functions do different things, we are going to keep them separate. That way, we can focus the functions on exactly and only the job we need each of them to do.

Pages that use dynamic routing can provide a getStaticPaths() next to their getStaticProps(). This function tells Next.js what values of the dynamic path segments to build pages for. We can provide those by using getAllPosts() and returning a list of objects that define each post’s slug.

-import { getPostBySlug } from '../../lib/api'
+import { getAllPosts, getPostBySlug } from '../../lib/api'

 export default function Post({ post }) { … }

 export function getStaticProps({ params }) { … }

+export function getStaticPaths() {
+  return {
+    fallback: false,
+    paths: getAllPosts().map(post => ({
+      params: {
+        slug: post.slug,
+      },
+    })),
+  }
+}

Since we parse the Markdown content in getPostBySlug(slug), we can render it on the page now. We need to use dangerouslySetInnerHTML for this step so Next.js can render the HTML behind post.body. Despite its name, it is safe to use the property in this scenario. Because we have full control over our posts, it is unlikely they are going to inject unsafe scripts.

 import { getAllPosts, getPostBySlug } from '../../lib/api'

 export default function Post({ post }) {
  const prettyDate = new Date(post.createdAt).toLocaleString('en-US', {
    month: 'short',
    day: '2-digit',
    year: 'numeric',
  })

   return (
     <div className="post">
       <h1>{post.title}</h1>

       <time dateTime={post.createdAt}>{prettyDate}</time>

-      {/ TODO: render body /}
+      <div dangerouslySetInnerHTML={{ __html: post.body }} />
     </div>
   )
 }

 export function getStaticProps({ params }) { … }

 export function getStaticPaths() { … }

If we follow one of the links from the post overview, we now get to that post’s own page.

Adding Authors

Now that we have posts wired up, we need to repeat the same steps for our authors. This time, we’ll use JSON instead of Markdown to describe them. We can mix different types of files in the same project like this whenever it makes sense. The helper functions we use to read the files take care of any differences for us. Pages can use these functions without knowing what format we store our content in.

First, create a directory called _authors/ and add a few author files to it. As we did with posts, name the files by each author’s slug. We’ll use that to look up authors later. In each file, we specify an author’s full name in a JSON object.

{
  "name": "Adrian Webber"
}

For now, having two authors in our project is enough.

To give them some more personality, let’s also add a profile picture for each author. We’ll put those static files in the public/ directory. By naming the files by the same slug, we can connect them using the implied convention alone. We could add the path of the picture to each author’s JSON file to link the two. By naming all files by the slugs, we can manage this connection without having to write it out. The JSON objects only need to hold information we can’t build with code.

When you’re done, your project directory should look something like this.

multi-author-blog/
├─ _authors/
│  ├─ adrian-webber.json
│  └─ megan-carter.json
├─ _posts/
│  └─ …
├─ pages/
│  └─ …
└─ public/
   ├─ adrian-webber.jpg
   └─ megan-carter.jpg
 

Same as with the posts, we now need helper functions to read all authors and get individual authors. The new functions getAllAuthors() and getAuthorBySlug(slug) also go in lib/api.js. They do almost exactly the same as their post counterparts. Because we use JSON to describe authors, we don’t need to parse any Markdown with remark here. We also don’t need gray-matter to parse frontmatter. Instead, we can use JavaScript’s built-in JSON.parse() to read the text contents of our files into objects.

const contents = fs.readFileSync(somePath, 'utf8')
// ⇒ looks like an object, but is a string
//   e.g. '{ "name": "John Doe" }'

const json = JSON.parse(contents)
// ⇒ a real JavaScript object we can do things with
//   e.g. { name: "John Doe" }

With that knowledge, our helper functions look like this:

 export function getAllPosts() { … }

 export function getPostBySlug(slug) { … }

+export function getAllAuthors() {
+  const authorsDirectory = path.join(process.cwd(), '_authors')
+  const filenames = fs.readdirSync(authorsDirectory)
+
+  return filenames.map(filename => {
+    const file = fs.readFileSync(path.join(process.cwd(), '_authors', filename), 'utf8')
+
+    // get data
+    const data = JSON.parse(file)
+
+    // get slug from filename
+    const slug = filename.replace(/.json/, '')
+
+    // return combined frontmatter and slug; build permalink
+    return {
+      ...data,
+      slug,
+      permalink: /authors/${slug},
+      profilePictureUrl: ${slug}.jpg,
+    }
+  })
+}
+
+export function getAuthorBySlug(slug) {
+  const file = fs.readFileSync(path.join(process.cwd(), '_authors', ${slug}.json), 'utf8')
+
+  const data = JSON.parse(file)
+
+  return {
+    ...data,
+    permalink: /authors/${slug},
+    profilePictureUrl: /${slug}.jpg,
+    slug,
+  }
+}

With a way to read authors into our application, we can now add a page that lists them all. Creating a new page under pages/authors/index.js gives us an /authors page on our site.

The helper functions take care of reading the files for us. This page component does not need to know authors are JSON files in the filesystem. It can use getAllAuthors() without knowing where or how it gets its data. The format does not matter as long as our helper functions return their data in a format we can work with. Abstractions like this let us mix different types of content across our application.

The index page for authors looks a lot like the one for posts. We get all authors in getStaticProps(), which passes them to the Authors component. That component maps over each author and lists some information about them. We don’t need to build any other links or URLs from the slug. The helper function already returns the authors in a usable format.

import Image from 'next/image'
import Link from 'next/link'

import { getAllAuthors } from '../../lib/api/authors'

export default function Authors({ authors }) {
  return (
    <div className="authors">
      <h1>Authors</h1>

      {authors.map(author => (
        <div key={author.slug}>
          <h2>
            <Link href={author.permalink}>
              <a>{author.name}</a>
            </Link>
          </h2>

          <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" />

          <Link href={author.permalink}>
            <a>Go to profile →</a>
          </Link>
        </div>
      ))}
    </div>
  )
}

export function getStaticProps() {
  return {
    props: {
      authors: getAllAuthors(),
    },
  }
}

If we visit /authors on our site, we see a list of all authors with their names and pictures.

The links to the authors’ profiles don’t lead anywhere yet. To add the profile pages, we create a file under pages/authors/[slug].js. Because authors don’t have any text content, all we can add for now are their names and profile pictures. We also need another getStaticPaths() to tell Next.js what slugs to build pages for.

import Image from 'next/image'

import { getAllAuthors, getAuthorBySlug } from '../../lib/api'

export default function Author({ author }) {
  return (
    <div className="author">
      <h1>{author.name}</h1>

      <Image alt={author.name} src={author.profilePictureUrl} height="80" width="80" />
    </div>
  )
}

export function getStaticProps({ params }) {
  return {
    props: {
      author: getAuthorBySlug(params.slug),
    },
  }
}

export function getStaticPaths() {
  return {
    fallback: false,
    paths: getAllAuthors().map(author => ({
      params: {
        slug: author.slug,
      },
    })),
  }
}

With this, we now have a basic author profile page that is very light on information.

At this point, authors and posts are not connected yet. We’ll build that bridge next so we can add a list of each authors’ posts to their profile pages.

Connecting Posts And Authors

To connect two pieces of content, we need to reference one in the other. Since we already identify posts and authors by their slugs, we’ll reference them with that. We could add authors to posts and posts to authors, but one direction is enough to link them. Since we want to attribute posts to authors, we are going to add the author’s slug to each post’s frontmatter.

 ---
 title: "Hello World!"
 excerpt: "This is my first blog post."
 createdAt: "2021-05-03"
+author: adrian-webber
 ---
 Hey, how are you doing? Welcome to my blog. In this post, …

If we keep it at that, running the post through gray-matter adds the author field to the post as a string:

const post = getPostBySlug("hello-world")
const author = post.author

console.log(author)
// "adrian-webber"

To get the object representing the author, we can use that slug and call getAuthorBySlug(slug) with it.

 const post = getPostBySlug("hello-world")
-const author = post.author
+const author = getAuthorBySlug(post.author)

 console.log(author)
 // {
 //   name: "Adrian Webber",
 //   slug: "adrian-webber",
 //   profilePictureUrl: "/adrian-webber.jpg",
 //   permalink: "/authors/adrian-webber"
 // }

To add the author to a single post’s page, we need to call getAuthorBySlug(slug) once in getStaticProps().

+import Image from 'next/image'
+import Link from 'next/link'

-import { getPostBySlug } from '../../lib/api'
+import { getAuthorBySlug, getPostBySlug } from '../../lib/api'

 export default function Post({ post }) {
   const prettyDate = new Date(post.createdAt).toLocaleString('en-US', {
     month: 'short',
     day: '2-digit',
     year: 'numeric',
   })

   return (
     <div className="post">
       <h1>{post.title}</h1>

       <time dateTime={post.createdAt}>{prettyDate}</time>

+      <div>
+        <Image alt={post.author.name} src={post.author.profilePictureUrl} height="40" width="40" />
+
+        <Link href={post.author.permalink}>
+          <a>
+            {post.author.name}
+          </a>
+        </Link>
+      </div>

       <div dangerouslySetInnerHTML={{ __html: post.body }}>
     </div>
   )
 }

 export function getStaticProps({ params }) {
+  const post = getPostBySlug(params.slug)

   return {
     props: {
-      post: getPostBySlug(params.slug),
+      post: {
+        ...post,
+        author: getAuthorBySlug(post.author),
+      },
     },
   }
 }

Note how we spread ...post into an object also called post in getStaticProps(). By placing author after that line, we end up replacing the string version of the author with its full object. That lets us access an author’s properties through post.author.name in the Post component.

With that change, we now get a link to the author’s profile page, complete with their name and picture, on a post’s page.

Adding authors to the post overview page requires a similar change. Instead of calling getAuthorBySlug(slug) once, we need to map over all posts and call it for each one of them.

+import Image from 'next/image'
+import Link from 'next/link'

-import { getAllPosts } from '../../lib/api'
+import { getAllPosts, getAuthorBySlug } from '../../lib/api'

 export default function Posts({ posts }) {
   return (
     <div className="posts">
       <h1>Posts</h1>

       {posts.map(post => {
         const prettyDate = new Date(post.createdAt).toLocaleString('en-US', {
           month: 'short',
           day: '2-digit',
           year: 'numeric',
         })

         return (
           <article key={post.slug}>
             <h2>
               <Link href={post.permalink}>
                 <a>{post.title}</a>
               </Link>
             </h2>

             <time dateTime={post.createdAt}>{prettyDate}</time>

+            <div>
+              <Image alt={post.author.name} src={post.author.profilePictureUrl} height="40" width="40" />
+
+              <span>{post.author.name}</span>
+            </div>

             <p>{post.excerpt}</p>

             <Link href={post.permalink}>
               <a>Read more →</a>
             </Link>
           </article>
         )
       })}
     </div>
   )
 }

 export function getStaticProps() {
   return {
     props: {
-      posts: getAllPosts(),
+      posts: getAllPosts().map(post => ({
+        ...post,
+        author: getAuthorBySlug(post.author),
+      })),
    }
  }
}

That adds the authors to each post in the post overview:

We don’t need to add a list of an author’s posts to their JSON file. On their profile pages, we first get all posts with getAllPosts(). We can then filter the full list for the ones attributed to this author.

import Image from 'next/image'
+import Link from 'next/link'

-import { getAllAuthors, getAuthorBySlug } from '../../lib/api'
+import { getAllAuthors, getAllPosts, getAuthorBySlug } from '../../lib/api'

 export default function Author({ author }) {
   return (
     <div className="author">
       <h1>{author.name}</h1>

       <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" />

+      <h2>Posts</h2>
+
+      <ul>
+        {author.posts.map(post => (
+          <li>
+            <Link href={post.permalink}>
+              <a>
+                {post.title}
+              </a>
+            </Link>
+          </li>
+        ))}
+      </ul>
     </div>
   )
 }

 export function getStaticProps({ params }) {
   const author = getAuthorBySlug(params.slug)

   return {
     props: {
-      author: getAuthorBySlug(params.slug),
+      author: {
+        ...author,
+        posts: getAllPosts().filter(post => post.author === author.slug),
+      },
     },
   }
 }

 export function getStaticPaths() { … }

This gives us a list of articles on every author’s profile page.

On the author overview page, we’ll only add how many posts they have written to not clutter the interface.

import Image from 'next/image'
import Link from 'next/link'

-import { getAllAuthors } from '../../lib/api'
+import { getAllAuthors, getAllPosts } from '../../lib/api'

 export default function Authors({ authors }) {
   return (
     <div className="authors">
       <h1>Authors</h1>

       {authors.map(author => (
         <div key={author.slug}>
           <h2>
             <Link href={author.permalink}>
               <a>
                 {author.name}
               </a>
             </Link>
           </h2>

           <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" />

+         <p>{author.posts.length} post(s)</p>

           <Link href={author.permalink}>
             <a>Go to profile →</a>
           </Link>
         </div>
       ))}
     </div>
   )
 }

 export function getStaticProps() {
   return {
     props: {
-      authors: getAllAuthors(),
+      authors: getAllAuthors().map(author => ({
+        ...author,
+        posts: getAllPosts().filter(post => post.author === author.slug),
+      })),
     }
   }
 }

With that, the Authors overview page shows how many posts each author has contributed.

And that’s it! Posts and authors are completely linked up now. We can get from a post to an author’s profile page, and from there to their other posts.

Summary And Outlook

In this article, we connected two related types of content through their unique slugs. Defining the relationship from post to author enabled a variety of scenarios. We can now show the author on each post and list their posts on their profile pages.

With this technique, we can add many other kinds of relationships. Each post might have a reviewer on top of an author. We can set that up by adding a reviewer field to a post’s frontmatter.

 ---
 title: "Hello World!"
 excerpt: "This is my first blog post."
 createdAt: "2021-05-03"
 author: adrian-webber
+reviewer: megan-carter
 ---
 Hey, how are you doing? Welcome to my blog. In this post, …

On the filesystem, the reviewer is another author from the _authors/ directory. We can use getAuthorBySlug(slug) to get their information as well.

 export function getStaticProps({ params }) {
   const post = getPostBySlug(params.slug)

   return {
     props: {
       post: {
         ...post,
         author: getAuthorBySlug(post.author),
+        reviewer: getAuthorBySlug(post.reviewer),
       },
     },
   }
 }

We could even support co-authors by naming two or more authors on a post instead of only a single person.

 ---
 title: "Hello World!"
 excerpt: "This is my first blog post."
 createdAt: "2021-05-03"
-author: adrian-webber
+authors:
+  - adrian-webber
+  - megan-carter
 ---
 Hey, how are you doing? Welcome to my blog. In this post, …

In this scenario, we could no longer look up a single author in a post’s getStaticProps(). Instead, we would map over this array of authors to get them all.

 export function getStaticProps({ params }) {
   const post = getPostBySlug(params.slug)

   return {
     props: {
       post: {
         ...post,
-        author: getAuthorBySlug(post.author),
+        authors: post.authors.map(getAuthorBySlug),
       },
     },
   }
 }

We can also produce other kinds of scenarios with this technique. It enables any kind of one-to-one, one-to-many, or even many-to-many relationship. If your project also features newsletters and case studies, you can add authors to each of them as well.

On a site all about the Marvel universe, we could connect characters and the movies they appear in. In sports, we could connect players and the teams they currently play for.

Because helper functions hide the data source, content could come from different systems. We could read articles from the filesystem, comments from an API, and merge them into our code. If some piece of content relates to another type of content, we can connect them with this pattern.

Further Resources

Next.js offers more background on the functions we used in their page on Data Fetching. It includes links to sample projects that fetch data from different types of sources.

If you want to take this starter project further, check out these articles:

Cloud adoption – An architectural introduction

Tharticle imageis article launches a new series exploring the cloud adoption architecture. It's focusing on presenting access to ways of mapping successful implementations for specific use cases.

It's an interesting challenge creating architectural content based on common customer adoption patterns. That's very different from most of the traditional marketing activities usually associated with generating content for the sole purpose of positioning products for solutions. When you're basing the content on actual execution in solution delivery, you're cutting out the chuff. 

How To Run A UX Audit For A Major EdTech Platform (Case Study)

The business world today is obsessed with user experience (UX) design. And for good reason: Every dollar invested in UX brings $100 in return. So, having some free time in quarantine, I decided to check whether one of the most evolving industries right now, education technology (EdTech), uses this potential of UX.

My plan was to choose one EdTech platform, audit its UX, and, if necessary, redesign it. I first looked at some major EdTech platforms (such as edX, Khan Academy, and Udemy), read user feedback about them, and then narrowed my scope to edX. Why did I choose edX? Simply because:

  • it’s non-profit,
  • it has more than 20 million users,
  • its UX has a lot of negative reviews.

Even from my quick UX check, I got an overview of the UX principles and UI solutions followed by global EdTech platforms right now (in my case, edX).

Overall, this UX audit and redesign concept would be of great use to UX designers, business owners, and marketing people because it presents a way to audit and fix a product’s most obvious usability issues. So, welcome to my edX audit.

Audit Structure

This audit consists of two parts. First, I surveyed edX users, learned their needs, and checked whether the platform meets them. In the second stage, I weighed edX’s website against the 10 usability heuristics identified by Jacob Nielsen. These heuristics are well-recognized UX guidelines — the bible, if you will, for any UX designer.

Ideally, a full-fledged UX audit would take weeks. I had a fixed scope, so I checked the platform’s home page, user profile, and search page. These are the most important pages for users. Just analyzing these few pages gave me more than enough insight for my redesign concept.

Part 1: Audit for User Needs

Good UX translates into satisfied users.

That’s where I started: identifying user needs. First, I analyzed statistical data about the platform. For this, you can use such well-known tools as Semrush and SimilarWeb and reviews from Trustpilot, Google Play, and Apple’s App Store.

Take SimilarWeb. The tool analyzes edX’s rank, traffic sources, advertising, and audience interests. “Computer Electronics” and “Technology” appear to be the most popular course categories among edX students.

For user feedback on edX, I went to Trustpilot (Google Play and the App Store are relevant only for analyzing mobile apps). I found that most users praise edX’s courses for their useful content, but complain about the platform’s UX — most often about the hard and time-consuming verification process and poor customer support.

Done with the analytical check, I moved on to user interviews. I went to design communities on Facebook and LinkedIn, looking for students of online courses, asking them to answer some of my quick questions. To everyone who responded, I sent a simple Google Form to capture their basic needs and what they value most when choosing an education platform.

Having received the answers, I created two user profiles for edX: potential user and long-time user. Here’s a quick illustration of these two types:

I identified these two kinds of users based on my survey. According to my findings, there are two common scenarios for how users select an educational course.

Learner 1 is mainly focused on choosing between different education platforms. This user type doesn’t need a specific course. They are visiting various websites, looking for a course that grabs their attention.

The second kind of learner knows exactly what course they want to take. Supposing they’ve chosen edX, they would need an effective search function to help them locate the course they need, and they’d need a convenient profile page to keep track of their progress.

Based on the edX user profiles, their needs, and the statistical data I gathered, I have outlined the five most common problems that the platform’s customers might face.

Problem 1: “Can I Trust This Website?”

Numerous factors determine a website’s credibility and trustworthiness: the logo, reviews, feedback, displayed prices, etc. Nielsen Norman Group covers the theory of it. Let’s focus on the practice.

So, what do we have here? edX’s current home page displays the logos of its university partners, which are visible at first glance and add credibility to the platform.

At the same time, the home page doesn’t highlight benefits of the platform or user feedback. This is often a deciding factor for users in choosing a platform.

Other approaches

It’s good to learn from competitors. Another EdTech platform, Khan Academy, demonstrates quite a different approach to website design. Its home page introduces the platform, talks about its benefits, and shows user feedback:

Problem 2: “Do I Have All of the Information I Need to Choose a Course?

Many a time, users just want to quickly scan the list of courses and then choose the best one based on the description.

edX’s course cards display the course name, institution, and certificate level. Yet, they could also provide essentials such as pricing, course rating, how many students are enrolled, start date, etc.

Proper description of elements is an essential part of UX, as mentioned in Jacob Nielsen’s sixth heuristic. The heuristic states that all information valuable to a user should always be available.

Other approaches

Looking at another EdTech platform, Udemy’s course cards display the course name, instructor, rating, number of reviews, and price.

Problem 3: “Can I Sign Up Easily?”

According to a study by Mirjam Seckler, completion time decreases significantly if a signup form follows basic usability guidelines. Users are almost twice as likely to sign up in their first try if there are no errors.

So, let’s have a deeper look at edX’s forms:

  1. They do not let you type your country’s name or your date of birth. Instead, you have to scroll through all of the options. (I am in the Ukraine, which is pretty far down the list.)
  2. They do not display the password you’ve inputted, even by request.
  3. They do not send an email to verify the address you’ve entered.
  4. They do not indicate with an asterisk which fields are required.

Speeding up the registration process is yet another crucial UX principle. To read more about it, look at Nielsen Norman Group’s usability guidelines for website forms.

Other approaches

Many websites let users enter data manually to speed up the application process. Another EdTech website, Udemy, has an option to show and hide the inputted password by request:

Problem 4: “Is On-Site Search Helpful?”

Search is one of the most used website features. Thus, it should be helpful, simple to use, and fast. Numerous usability studies show the importance of helpful search for massive online open courses (MOOCs).

In this regard, I’ve analyzed edX’s search. I started from page loading. Below is a screenshot from Google PageSpeed, which shows that the platform’s search speed has a grade of 12 out of 100.

Let’s now move to searching in a specific category. In its current design, edX has no filtering. After choosing a category (for example, electronics courses), users need to scroll through the list to find what they want. And some categories have more than 100 items.

Other approaches

EdTech platform Coursera has visible filtering on its website, displaying all of the options to filter from in a category:

Problem 5: “Should I Finish This Course?”

Researchers don’t stop stressing that EdTech platforms have, on average, higher retention rates than other websites. Therefore, tracking user progress and motivation is critical for online courses. These principles are pretty simple yet effective.

That is what edX’s user profile looks like:

Other approaches

Khan Academy’s user profile displays various statistics, such as join date, points earned, and longest learning streak. It might motivate the user to continue learning and to track their success.

Part 2: Audit for 10 Usability Heuristics

We’ve finished analyzing the most common user needs on edX. It’s time to move to the 10 usability criteria identified by Nielsen Norman Group, a UX research and consulting firm trusted by leading organizations worldwide.

You can do a basic UX checkup of your website using the 10 heuristics even if you aren’t a UX designer. Nielsen Norman Group’s website gives a lot of examples, videos, and instructions for each heuristic. This Notion checklist makes it even more convenient. It includes vital usability criteria required for any website. It’s a tool used internally at Fulcrum (where I work), but I thought it would be good to share it with the Smashing Magazine audience. It includes over a hundred criteria, and because it’s in Notion, you can edit it and customize it however you want.

Heuristic 1: Visibility of System Status

The first heuristic is to always keep users informed. Simply put, a website should provide users with feedback whenever an action is completed. For example, you will often see a “Success” message when downloading a file on a website.

In this regard, edX’s current course cards could be enhanced. Right now, a card does not tell users whether the course is available. Users have to click on the card to find out.

Possible approach

If some courses aren’t available, indicate that from the start. You could use bright labels with “available”/“not available” messages.

Heuristic 2: Match Between System and the Real World

The system should speak the user’s language. It should use words, phrases, and symbols that are familiar to the average visitor. And the information should appear in a logical order.

This is the second criterion of Jacob Nielsen. edX’s website pretty much follows this principle, using common language, generally accepted symbols, and familiar signs.

Possible approach

Another good practice would be to break down courses by sections, and add easy-to-understand icons.

Heuristic 3: User Control and Freedom

This heuristic stresses that users always should have a clear way out when they do something by mistake, something like an undo or return option.

edX makes it impossible to change your username once it’s been set up. Many websites limit the options for changing a username for security reasons. Still, it might be more user-friendly to make it changeable.

Possible approach

Some websites allow users to save data, a status, or a change whenever they want. A good practice would be to offer customers alternative options, like to add or remove a course or to save or edit their profile.

Heuristic 4: Consistency and Standards

According to this fourth UX criterion, design elements should be consistent and predictable. For example, symbols and images should be unified across the UI design of a platform.

Broadly speaking, there are two types of consistencies: internal and external. Internal consistency refers to staying in sync with a product (or a family of products). External consistency refers to adhering to the standards within an industry (for example, shopping carts having the same logic across e-commerce websites).

edX sometimes breaks internal consistency. Case in point just below: The “Explore” button looks different. Two different-looking buttons (or any other elements) that perform the same function might add visual noise and worsen the user experience. This issue might not be critical, but it contributes to the overall UX of the website.

Heuristic 5: Error Prevention

Good design prevents user error. By helping users avoid errors, designers save them time and prevent frustration.

For instance, on edX, if you make a typo in your email address, it’s visible only after you try to verify it.

Possible approach

Granted, live validation is not always good for UX. Some designers consider it problematic, arguing that it distracts users and causes confusion. Others believe that live validation has a place in UX design.

In any case, whether you’re validating live or after the “Submit” button has been clicked, keep your users and their goals in mind. Your task is to make their experience as smooth as possible.

Heuristic 6: Recognition Rather Than Recall

Users should not have to memorize information you’ve shown them before. That’s another UX guideline from Nielsen Norman Group. Colors and icons (like arrows) help users process information better.

edX’s home page displays university logos, but not the universities’ full names, which illustrates this point. Also, the user profile page doesn’t tell you which courses you’ve completed.

Possible approach

The platform’s UX could be improved by showing courses that users have already done and recommending similar ones.

Heuristic 7: Flexibility and Efficiency of Use

According to this UX principle, speed up interaction wherever possible by using elements called accelerators. Basically, use any options or actions that speed up the whole process.

edX doesn’t provide filtering when users search for a course. Its absence could increase the time and effort users take to find the course they need.

Possible approach

Search is one of the critical stages of user conversion. If users can find what they want, they will be much closer to becoming customers. So, use filters to help users find courses more quickly and easily.

Heuristic 8: Aesthetic and Minimalist Design

This heuristic tells us to “remove unnecessary elements from the user interface and to maximize the signal-to-noise ratio of the design” (the signal being information relevant to the user, and the noise being irrelevant information).

Simply put, every element should tell a story, like a mosaic. Designers communicate, not decorate.

Comparing the current design of edX’s home page to the previous one, we can see a huge improvement. The main photo is now much more relevant to the platform’s mission. edX also added insights into how many users and courses it has.

Heuristic 9: Help Users Recognize, Diagnose, and Recover From Errors

This heuristic states that errors should be expressed in simple, explanatory language to the user. It’s also good to clearly explain why an error occurred in the first place.

edX’s 404 page serves its purpose overall. First, it explains to the user the problem (“We can’t seem to find the page you’re looking for”) and suggests a solution (giving links to the home page, search function, and course list). It also recommends popular courses.

Heuristic 10: Help and Documentation

This last heuristic is about the necessity of support and documentation on any website. There are many forms of help and documentation, such as onboarding pages, walkthroughs, tooltips, chats, and chatbots.

edX has links to a help center hidden in the footer. It’s divided into sections, and users can use a search bar to find information. The search does a good job of auto-suggesting topics that might be useful.

Unfortunately, users can’t go back to the home page from the help center by clicking the logo. There is no direct way to get back to the home page from there.

Possible approach

Enable users to return to the home page wherever they want on the website.

eDX Redesign Concept

Based on my UX findings, I resdesigned the platform, focusing on the home page, user profiles, and search results page. You can see full images of the redesign in Figma.

Home Page

1. Signal-to-Noise Ratio

First things first: To meet usability heuristic 8, I’ve made the whole page more minimalist and added space between its elements.

edX has the grand mission of “education for everyone, everywhere”, so I decided to put this on the home page, plain and bold.

I also switched the images to better reflect the story presented in the text. I expressed the mission with these new illustrations:

2. Course Cards

The “New Courses” section below highlights the latest courses.

I also added some details that edX’s cards currently do not display. This made the cards more descriptive, showing essential information about each course.

I also used icons to show the most popular subjects.

3. Credibility and Trust

I added a fact sheet to show the platform’s credibility and authority:

In addition, I freshened up the footer, reshaping the languages bar to be more visible to users.

Helpful Search

1. Search Process

In edX’s current design, users don’t see the options available while searching. So, I designed a search function with auto-suggestion. Now, users just need to type a keyword and choose the most relevant option.

2. Search Filters

I added a left sidebar to make it easy to filter results. I also updated the UI and made the course cards more descriptive.

User Profile

As mentioned in the audit section, it’s essential to motivate users to continue studying. Inspired by Khan Academy, I added a progress bar to user profiles. Now, a profile shows how many lessons are left before the user completes a course.

I put the navigation above so that it can be easily seen. Also, I updated the user profile settings, leaving the functionality but modifying the colors.

Conclusion

A UX audit is a simple and efficient way to check whether design elements are performing their function. It’s also a good way to look at an existing design from a fresh perspective.

This case presented me with several lessons. First, I see that the websites in one of the most topical industries right now could have their UX updated. Learning something new is hard, but without proper UX design, it’s even harder.

The audit also showed why it’s crucial to understand, analyze, and meet user needs. Happy users are devoted users.

Feature Flag, What? Why? How?

Velocity in agile development measures the quantity of work a team can accomplish in a sprint. It can be measured in story points, hours or days. The higher the velocity of a team, the more features it delivers, the more value it brings to customers. Sprint velocity is a good measure in sprint project management to evaluate and estimate team productivity.

The measure of the velocity is based on multiple factors: the continuous integration (CI) process, the time to qualify the code changes, to test the regression, the security, the delivery, etc…

Automation Testing Pyramid Today

The testing pyramid is a familiar concept in software engineering. The model drew greater attention in Mike Cohn’s 2009 book Succeeding with Agile.

The bottom level of the pyramid consists of unit tests. Unit tests are written by developers and cover methods and functions behaviors, using test doubles to mock inputs, outputs and external dependencies. 

Integration Patterns in Microservices World

Integration, Events, and Microservice Patterns

Overview

It is intended for architects and developers to use this information to guide their integration solutions. Once an integration style has been selected, they should review the associated integration style section to understand the typical patterns relevant to that integration style, abide by the constraints and review the implementation considerations.

Understanding “Integration” – Applications, Integration, and Microservices

There is an important distinction around the responsibilities of an integration system and applications. Integration facilitates the transmission of information between systems and may be realized through the use of technologies such as messaging systems, API gateways, and their associated protocols (MQ, AMQP, HTTP, FTP). Over the years, many technology vendors have implemented additional capabilities within their integration products that have enabled developers to add business logic embedded within the integration solution. This has resulted in business logic being spread outside of the application layer and integration an integration system that is often managed and maintained by a single team resulting in a resource bottleneck.

The Rise Of Design Thinking As A Problem Solving Strategy

Having spent the last 20 years in the world of educational technology working on products for educators and students, I have learned to understand teachers and administrators as designers themselves, who use a wide set of tools and techniques to craft learning experiences for students. I have come to believe that by extending this model and framing all users as designers, we are able to mine our own experiences to gain a deeper empathy for their struggles. In doing so, we can develop strategies to set our user-designers up to successfully deal with change and uncertainty.

If you are a designer, or if you have worked with designers any time in the last decade, you are probably familiar with the term “design thinking.” Typically, design thinking is represented by a series of steps that looks something like this:

There are many variations of this diagram, reflective of the multitude of ways that the process can be implemented. It is typically a months-long undertaking that begins with empathy: we get to know a group of people by immersing ourselves in a specific context to understand their tasks, pain points, and motivations. From there, we take stock of our observations, looking for patterns, themes, and opportunities, solidifying the definition of the problem we wish to solve. Then, we iteratively ideate, prototype, and test solutions until we arrive at one we like (or until we run out of time).

Ultimately, the whole process boils down to a simple purpose: to solve a problem. This is not a new purpose, of course, and not unique to those of us with “Designer” in our job titles. In fact, while design thinking is not exactly the same as the scientific method we learned in school, it bears an uncanny resemblance:

By placing design thinking within this lineage, we equate the designer with the scientist, the one responsible for facilitating the discovery and delivery of the solution.

At its best, design thinking is highly collaborative. It brings together people from across the organization and often from outside of it, so that a diverse group, including those whose voices are not usually heard, can participate. It centers the needs and emotions of those we hope to serve. Hopefully, it pulls us out of our own experiences and biases, opening us up to new ways of thinking and shining a light on new perspectives. At its worst, when design thinking is dogmatically followed or cynically applied, it becomes a means of gatekeeping, imposing a rigid structure and set of rules that leave little room for approaches to design that do not conform to an exclusionary set of cultural standards.

Its relative merits, faults, and occasional high-profile critiques notwithstanding, design thinking has become orthodoxy in the world of software development, where not using it feels tantamount to malpractice. No UX Designer’s portfolio is complete without a well-lit photo capturing a group of eager problem solvers in the midst of the “Define” step, huddled together, gazing thoughtfully at a wall covered in colorful sticky notes. My colleagues and I use it frequently, sticky notes and all, as we work on products in EdTech.

Like “lean,” the design thinking methodology has quickly spread beyond the software industry into the wider world. Today you can find it in elementary schools, in nonprofits, and at the center of innovation labs housed in local governments.

Amidst all of the hoopla, it is easy to overlook a central assumption of design thinking, which seems almost too obvious to mention: the existence of a solution. The process rests on the premise that, once the steps have been carried out, the state of the problem changes from ‘unsolved’ to ‘solved.’ While this problem-solution framework is undeniably effective, it is also incomplete. If we zoom out, we can see the limits of our power as designers, and then we can consider what those limits mean for how we approach our work.

Chaos And The Limits Of Problem Solving

An unchecked belief in our ability to methodically solve big problems can lead to some pretty grandiose ideas. In his book, Chaos: Making a New Science, James Gleick describes a period in the 1950s and ’60s when, as computing and satellite technologies continued to advance, a large, international group of scientists embarked on a project that, in hindsight, sounds absurd. Their goal was not only to accurately predict, but also to control the weather:

“There was an idea that human society would free itself from weather’s turmoil and become its master instead of its victim. Geodesic domes would cover cornfields. Airplanes would seed the clouds. Scientists would learn how to make rain and how to stop it.”

— “Chaos: Making a New Science,” James Gleick

It is easy to scoff at their hubris now, but at the time it was a natural result of an ever-heightening faith that, with science, no problem is too big to solve. What those scientists did not account for is a phenomenon commonly known as the butterfly effect, which is now a central pillar of the field of chaos theory. The butterfly effect describes the inherent volatility that arises in complex and interconnected systems. It gets its name from a famous illustration of the principle: a butterfly flapping its wings and creating tiny disturbances in the air around it on one side of the globe today can cause a hurricane tomorrow on the other. Studies have shown that the butterfly effect impacts everything in society from politics and the economy to trends in fashion.

Our Chaotic Systems

If we accept that, like the climate, the social systems in which we design and build solutions are complex and unpredictable, a tension becomes apparent. Design thinking exists in a context that is chaotic and unpredictable by nature, and yet the act of predicting is central. By prototyping and testing, we are essentially gathering evidence about what the outcome of our design will be, and whether it will effectively solve the problem we have defined. The process ends when we feel confident in our prediction and happy with the result.

I want to take pains to point out again that this approach is not wrong! We should trust the process to confirm that our designs are useful and usable in the immediate sense. At the same time, whenever we deliver a solution, we are like the butterfly flapping its wings, contributing (along with countless others) to a constant stream of change. So while the short-term result is often predictable, the longer-term outlook for the system as a whole, and for how long our solution will hold as the system changes, is unknowable.

Impermanence

As we use design thinking to solve problems, how do we deal with the fact that our solutions are built to address conditions that will change in ways we can’t plan for?

One basic thing we can do is to maintain awareness of the impermanence of our work, recognizing that it was built to meet the needs of a specific moment in time. It is more akin to a tree fort constructed in the woods than to a castle fortress made from stone. While the castle may take years to build and last for centuries, impervious to the weather while protecting its inhabitants from all of the chaos that exists outside its walls, the tree fort, even if well-designed and constructed, is directly connected to and at the mercy of its environment. While a tree fort may shelter us from the rain, we do not build it with the expectation that it will last forever, only with the hope that it will serve us well while it’s here. Hopefully, through the experience of building it, we continue to learn and improve.

The fact that our work is impermanent does not diminish its importance, nor does it give us the license to be sloppy. It means that the ability to quickly and consistently adapt and evolve without sacrificing functional or aesthetic quality is core to the job, which is one reason why design systems, which provide consistent and high-quality reusable patterns and components, are crucial.

Designing For User-Designers

A more fundamental way to deal with the impermanence of our work is to rethink our self-image as designers. If we identify only as problem solvers, then our work becomes obsolete quickly and suddenly as conditions change, while in the meantime our users must wait helplessly to be rescued with the next solution. In reality, our users are compelled to adapt and design their own solutions, using whatever tools they have at their disposal. In effect, they are their own designers, and so our task shifts from delivering full, fixed solutions to providing our user-designers with useful and usable tools specific to their needs.

In thinking from this perspective, we can gain empathy for our users by understanding our place as equals on a continuum, each of us relying on others, just as others rely on us.

Key Principles To Center The Needs Of User-Designers

Below are some things to consider when designing for user-designers. In the spirit of the user-designer continuum and of finding the universal in the specific, in the examples below I draw on my experience from both sides of the relationship. First, from my work as a designer in the EdTech space, in which educators rely on people like me to produce tools that enable them to design learning experiences for students. Second, as a user of the products, I rely on them in my daily UX work.

1. Don’t Lock In The Value

It is crucial to have a clear understanding of why someone would use your product in the first place, and then make sure not to get in the way. While there is a temptation to keep that value contained so that users must remain in your product to reap all of the benefits, we should resist that mindset.

Remember that your product is likely just one tool in a larger set, and our users rely on their tools to be compatible with each other as they design their own coherent, holistic solutions. Whereas the designer-as-problem-solver is inclined to build a self-contained solution, jealously locking value within their product, the designer-for-designers facilitates the free flow of information and continuity of task completion between tools however our user-designers choose to use them. By sharing the value, not only do we elevate its source, we give our users full use of their toolbox.

An Example As A Designer Of EdTech Products:

In student assessment applications, like in many other types of applications, the core value is the data. In other words, the fundamental reason schools administer assessments is to learn about student achievement and growth. Once that data is captured, there are all sorts of ways we can then use it to make intelligent, research-based recommendations around tasks like setting student goals, creating instructional groups, and assigning practice. To be clear, we do try very hard to support all of it in our products, often by using design thinking. Ultimately, though, it all starts with the data.

In practice, teachers often have a number of options to choose from when completing their tasks, and they have their own valid reasons for their preferences. Anything from state requirements to school policy to personal working style may dictate their approach to, say, student goal setting. If — out of a desire to keep people in our product — we make it extra difficult for teachers to use data from our assessments to set goals outside of our product (say, in a spreadsheet), then instead of increasing our value, we have added inconvenience and frustration. The lesson, in this case, is not to lock up the data! Ironically, by hoarding it, we make it less valuable. By providing educators with easy and flexible ways to get it out, we unlock its power.

An Example As A User Of Design Tools:

I tend to switch between tools as I go through the design thinking process based on the core value each tool provides. All of these tools are equally essential to the process, and I count on them to work together as I move between phases so that I don’t have to build from scratch at every step. For example, the core value I get from Sketch is mostly in the “Ideation” phase, in that it allows me to brainstorm quickly and freely so that I can try out multiple ideas in a short amount of time. By making it easy for me to bring ideas from that product into a more heavy-duty prototyping application like Axure, instead of locking them inside, Sketch saves me time and frustration and increases my attachment to it. If, for competitive reasons, those tools ceased to cooperate, I would be much more likely to drop one or both.

2. Use Established Patterns

It is always important to remember Jakob’s Law, which states simply that users spend more time on other sites than they spend on yours. If they are accustomed to engaging with information or accomplishing a task a certain way and you ask them to do it differently, they will not view it as an exciting opportunity to learn something new. They will be resentful. Scaling the learning curve is usually painful and frustrating. While it is possible to improve or even replace established patterns, it’s a very tall order. In a world full of unpredictability, consistent and predictable patterns among tools create harmony between experiences.

An Example As A Designer Of EdTech Products:

By following conventions around data visualization in a given domain, we make it easy for users to switch and compare between sources. In the context of education, it is common to display student progress in a graph of test scores over time, with the score scale represented on the vertical axis and the timeline along the horizontal axis. In other words, a scatter plot or line graph, often with one or two more dimensions represented, maybe by color or dot size. Through repeated, consistent exposure, even the most data-phobic teachers can easily and immediately interpret this data visualization and craft a narrative around it.

You could hold a sketching activity during the “Ideate” phase of design thinking in which you brainstorm dozens of other ways to present the same information. Some of those ideas would undoubtedly be interesting and cool, and might even surface new and useful insights. This would be a worthwhile activity! In all likelihood, though, the best decision would not be to replace the accepted pattern. While it can be useful to explore other approaches, ultimately the most benefit is usually derived from using patterns that people already understand and are used to across a variety of products and contexts.

An Example As A User Of Design Tools:

In my role, I often need to quickly learn new UX software, either to facilitate collaboration with designers from outside of my organization or when my team decides to adopt something new. When that happens, I rely heavily on established patterns of visual language to quickly get from the learning phase to the productive phase. Where there is consistency, there is relief and understanding. Where there is a divergence for no clear reason, there is frustration. If a product team decided to rethink the standard alignment palette, for example, in the name of innovation, it would almost certainly make the product more difficult to adopt while failing to provide any benefit.

3. Build For Flexibility

As an expert in your given domain, you might have strong, research-based positions on how certain tasks should be done, and a healthy desire to build those best practices into your product. If you have built up trust with your users, then adding guidance and guardrails directly into the workflow can be powerful. Remember, though, that it is only guidance. The user-designer knows when those best practices apply and when they should be ignored. While we should generally avoid overwhelming our users with choices, we should strive for flexibility whenever possible.

An Example As A Designer Of EdTech Products

Many EdTech products provide mechanisms for setting student learning goals. Generally, teachers appreciate being given recommendations and smart defaults when completing this task, knowing that there is a rich body of research that can help determine a reasonable range of expectations for a given student based on their historical performance and the larger data set from their peers. Providing that guidance in a simple, understandable format is generally beneficial and appreciated. But, we as designers are removed from the individual students and circumstances, as well as the ever-changing needs and requirements driving educators’ goal-setting decisions. We can build recommendations into the happy path and make enacting them as painless as possible, but the user needs an easy way to edit our guidance or to reject it altogether.

An Example As A User Of Design Tools:

The ability to create a library of reusable objects in most UX applications has made them orders of magnitude more efficient. Knowing that I can pull in a pre-made, properly-branded UI element as needed, rather than creating one from scratch, is a major benefit. Often, in the “Ideate” phase of design thinking, I can use these pre-made components in their fully generic form simply to communicate the main idea and hierarchy of a layout. But, when it’s time to fill in the details for high-fidelity prototyping and testing, the ability to override the default text and styling, or even detach the object from its library and make more drastic changes, may become necessary. Having the flexibility to start quickly and then progressively customize lets me adapt rapidly as conditions change, and helps make moving between the design thinking steps quick and easy.

4. Help Your User-Designers Build Empathy For Their Users

When thinking about our users as designers, one key question is: who are they designing for? In many cases, they are designing solutions for themselves, and so their designer-selves naturally empathize with and understand the problems of their user-selves. In other cases, though, they are designing for another group of people altogether. In those situations, we can look for ways to help them think like designers and develop empathy for their users.

An Example As A Designer Of EdTech Products:

For educators, the users are the students. One way to help them center the needs of their audience when they design experiences is to follow the standards of Universal Design for Learning, equipping educators to provide instructional material with multiple means of engagement (i.e., use a variety of strategies to drive motivation for learning), multiple means of representation (i.e., accommodate students’ different learning styles and backgrounds), and multiple means of action and expression (i.e., support different ways for students to interact with instructional material and demonstrate learning). These guidelines open up approaches to learning and nudge users to remember that all of the ways their audience engages with practice and instruction must be supported.

An Example As A User Of Design Tools:

Anything a tool can do to encourage design decisions that center accessibility is hugely helpful, in that it reminds us to consider those who face the most barriers to using our products. While some commonly-used UX tools do include functionality for creating alt-text for images, setting a tab order for keyboard navigation, and enabling responsive layouts for devices of various sizes, there is an opportunity for these tools to do much more. I would love to see built-in accessibility checks that would help us identify potential issues as early in the process as possible.

Conclusion

Hopefully, by applying the core principles of unlocking value, leveraging established patterns, understanding the individual’s need for flexibility, and facilitating empathy in our product design, we can help set our users up to adapt to unforeseen changes. By treating our users as designers in their own right, not only do we recognize and account for the complexity and unpredictability of their environment, we also start to see them as equals.

While those of us with the word “Designer” in our official job title do have a specific and necessary role, we are not gods, handing down solutions from on high, but fellow strugglers trying to navigate a complex, dynamic, stormy world. Nobody can control the weather, but we can make great galoshes, raincoats, and umbrellas.

Further Reading

  • If you’re interested in diving into the fascinating world of chaos theory, James Gleick’s book Chaos: Making a New Science, which I quoted in this article, is a wonderful place to start.
  • Jon Kolko wrote a great piece in 2015 on the emergence of design thinking in business, in which he describes its main principles and benefits. In a subsequent article from 2017, he considers the growing backlash as organizations have stumbled and taken shortcuts when attempting to put theory into practice, and what the lasting impact may be. An important takeaway here is that, in treating everyone as a designer, we run the risk of downplaying the importance of the professional Designer’s specific skill set. We should recognize that, while it is useful to think of teachers (or any of our users) as designers, the day-to-day tools, methods, and goals are entirely different.
  • In the article Making Sense in the Data Economy, Hugh Dubberly and Paul Pangaro describe the emerging challenges and complexities of the designer’s role in moving from the manufacture of physical products to the big data frontier. With this change, the focus shifts from designing finished products (solutions) to maintaining complex and dynamic platforms, and the concept of “meta-design” — designing the systems in which others operate — emerges.
  • To keep exploring the ever-evolving strategies of designing for designers, search Smashing Magazine and your other favorite UX resources for ideas on interoperability, consistency, flexibility, and accessibility!

Smashing Online Workshops (July-October 2021)

How do we build and establish a successful design system? What about modern CSS and JavaScript? What’s the state of HTML Email? And what are new, smart design patterns we could use? What will it take us to move to TypeScript or Vue.js? With our line-up of online workshops, we try to answer these questions well.

Our workshops bring in knowledgeable, kind folks from the community to explore real-life solutions to real-life problems, live, with you. All sessions are broken down into 2.5h-segments across days, so you always have time to ask questions, share your screen and get immediate feedback.

Meet Smashing Online Workshops: live, interactive sessions on frontend & UX.

In fact, live discussions and interactive exercises are at the very heart of every workshop, with group work, homework, reviews and live interaction with people around the world. Plus, you get all video recordings of all sessions, so you can re-watch at any time, in your comfy chair in familiar comfort of your workspace.

Upcoming Workshops (July-October 2021)

No pre-recorded sessions, no big picture talks. Our workshops take place live and span multiple days. They are split into 2.5h-sessions, plus you’ll get all workshop video recordings, slides and a friendly Q&A in every session.

Pssst! We also have friendly bundles for larger teams and agencies. 🎁

Workshops in July–August

Meet our friendly frontend & UX workshops. Boost your skills online and learn from experts — live.

Workshops in September–October

What Are Online Workshops Like?

Do you experience Zoom fatigue as well? After all, who really wants to spend more time in front of their screen? That’s exactly why we’ve designed the online workshop experience from scratch, accounting for the time needed to take in all the content, understand it and have enough time to ask just the right questions.

In our workshops, everybody is just a slightly blurry rectangle on the screen; everybody is equal, and invited to participate.

Our online workshops take place live and span multiple days across weeks. They are split into 2.5h-sessions, and in every session there is always enough time to bring up your questions or just get a cup of tea. We don’t rush through the content, but instead try to create a welcoming, friendly and inclusive environment for everyone to have time to think, discuss and get feedback.

There are plenty of things to expect from a Smashing workshop, but the most important one is focus on practical examples and techniques. The workshops aren’t talks; they are interactive, with live conversations with attendees, sometimes with challenges, homework and team work.

Of course, you get all workshop materials and video recordings as well, so if you miss a session you can re-watch it the same day.

TL;DR

  • Workshops span multiple days, split in 2.5h-sessions.
  • Enough time for live Q&A every day.
  • Dozens of practical examples and techniques.
  • You’ll get all workshop materials & recordings.
  • All workshops are focused on frontend & UX.
  • Get a workshop bundle and save $250 off the price.

Thank You!

We hope that the insights from the workshops will help you improve your skills and the quality of your work. A sincere thank you for your kind, ongoing support and generosity — for being smashing, now and ever. We’d be honored to welcome you.

Automating Screen Reader Testing On macOS Using Auto VO

If you’re an accessibility nerd like me, or just curious about assistive technology, you’ll dig Auto-VO. Auto-VO is a node module and CLI for automated testing of web content with the VoiceOver screen reader on macOS.

I created Auto VO to make it easier for developers, PMs, and QA to more quickly perform repeatable, automated tests with real assistive technology, without the intimidation factor of learning how to use a screen reader.

Let’s Go!

First, let’s see it in action, and then I’ll go into more detail about how it works. Here’s running auto-vo CLI on smashingmagazine.com to get all the VoiceOver output as text.

$ auto-vo --url https://smashingmagazine.com --limit 200 > output.txt
$ cat output.txt
link Jump to all topics
link Jump to list of all articles
link image Smashing Magazine
list 6 items
link Articles
link Guides 2 of 6
link Books 3 of 6
link Workshops 4 of 6
link Membership 5 of 6
More menu pop up collapsed button 6 of 6
end of list
end of navigation
...(truncated)

Seems like a reasonable page structure: we’ve got skip navigation links, well-structured lists, and semantic navigation. Great work! Let’s dig a little deeper though. How’s the heading structure?

$ cat output.txt | grep heading
heading level 2 link A Complete Guide To Accessibility Tooling
heading level 2 link Spinning Up Multiple WordPress Sites Locally With DevKinsta
heading level 2 link Smashing Podcast Episode 39 With Addy Osmani: Image Optimization
heading level 2 2 items A SMASHING GUIDE TO Accessible Front-End Components
heading level 2 2 items A SMASHING GUIDE TO CSS Generators & Tools
heading level 2 2 items A SMASHING GUIDE TO Front-End Performance 2021
heading level 4 LATEST POSTS
heading level 1 link When CSS Isn’t Enough: JavaScript Requirements For Accessible Components
heading level 1 link Web Design Done Well: Making Use Of Audio
heading level 1 link Useful Front-End Boilerplates And Starter Kits
heading level 1 link Three Front-End Auditing Tools I Discovered Recently
heading level 1 link Meet :has, A Native CSS Parent Selector (And More)
heading level 1 link From AVIF to WebP: A New Smashing Book By Addy Osmani

Hmm! Something’s a little funky with our heading hierarchy. We ought to see an outline, with one heading level one and an ordered hierarchy after that. Instead, we see a bit of a mishmash of level 1, level 2, and an errant level 4. This needs attention since it impacts screen reader users' experience navigating the page.

Having the screen reader output as text is great because this sort of analysis becomes much easier.

Some Background

VoiceOver is the screen reader on macOS. Screen readers let people read application content aloud, and also interact with content. That means that people with low vision or who are blind can in theory access content, including web content. In practice though, 98% of landing pages on the web have obvious errors that can be captured with automated testing and review.

There are many automated testing and review tools out there, including AccessLint.com for automated code review (disclosure: I built AccessLint), and Axe, a common go-to for automation. These tools are important and useful, but they are only part of the picture. Manual testing is equally or perhaps more important, but it’s also more time-consuming and can be intimidating.

You may have heard guidance to "just turn on your screen reader and listen" to give you a sense of the blind experience. I think this is misguided. Screen readers are sophisticated applications that can take months or years to master, and are overwhelming at first. Using it haphazardly to simulate the blind experience might lead you to feel sorry for blind people, or worse, try to "fix" the experience the wrong ways.

I’ve seen people panic when they enable VoiceOver, not knowing how to turn it off. Auto-VO manages the lifecycle of the screen reader for you. It automates the launch, control, and closing of VoiceOver, so you don’t have to. Instead of trying to listen and keep up, the output is returned as text, which you can then read, evaluate, and capture for later as a reference in a bug or for automated snapshotting.

Usage

Caveat

Right now, because of the requirement to enable AppleScript for VoiceOver, this may require custom configuration of CI build machines to run.

Scenario 1: QA & Acceptance

Let’s say I (the developer) have a design with blueline annotations - where the designer has added descriptions of things like accessible name and role. Once I’ve built the feature and reviewed the markup in Chrome or Firefox dev tools, I want to output the results to a text file so that when I mark the feature as complete, my PM can compare the screen reader output with the design specs. I can do that using the auto-vo CLI and outputting the results to a file or the terminal. We saw an example of this earlier in the article:

$ auto-vo --url https://smashingmagazine.com --limit 100
Scenario 2: Test Driven Development

Here I am again as the developer, building out my feature with a blueline annotated design. I want to test drive the content so that I don’t have to refactor the markup afterward to match the design. I can do that using the auto-vo node module imported into my preferred test runner, e.g. Mocha.

$ npm install --save-dev auto-vo
import { run } from 'auto-vo';
import { expect } from 'chai';

describe('loading example.com', async () => {
  it('returns announcements', async () => {
    const options = { url: 'https://www.example.com', limit: 10, until: 'Example' };

    const announcements = await run(options);

    expect(announcements).to.include.members(['Example Domain web content']);
  }).timeout(5000);
});

Under the Hood

Auto-VO uses a combination of shell scripting and AppleScript to drive VoiceOver. While digging into the VoiceOver application, I came across a CLI that supports starting VoiceOver from the command line.

/System/Library/CoreServices/VoiceOver.app/Contents/MacOS/VoiceOverStarter

Then, a series of JavaScript executables manage the AppleScript instructions to navigate and capture VoiceOver announcements. For example, this script gets the last phrase from the screen reader announcements:

function run() {
  const voiceOver = Application('VoiceOver');
  return voiceOver.lastPhrase.content();
}

In Closing

I’d love to hear your experience with auto-vo, and welcome contributions on GitHub. Try it out and let me know how it goes!