Peter's First Day

My name is Peter Boin, and this week was my first working with Kogan as a Software Engineer.

I’ve come from almost a decade of work in the Automotive industry, testing ABS/ESC applications, and coding for Body Control Modules.

This is quite a change for me, but so far I’ve been very impressed.

The interview process was seamless, friendly, and respectful. I particularly enjoyed coding being a strong part of the interview process, it clearly indicated that that’s what I’d be hired for.

The team at Kogan.com were very interested in what my passions were, even though they had little to do with web development or design (specifically). I see now that they could clearly see that my passions were not too different to those that drive the dedicated and talented engineers already here.

My first day was started with what you’d expect, a brief tour around the facilities; spacious offices, break room, and of course, the development area.

It was good to see that everybody was very busy, but still considerate and friendly.

I was also asked what hardware I would prefer to use, Windows, Mac, or Linux. “Linux!” I cried (literally), but that’s just me. This particular luxury of choice is something my last industry lacked, so I was particularly excited about working with my prefered operating system.

At 10am sharp we had a “stand-up”, which is a variation on Agile’s SCRUM. This is a fast-paced and focussed agenda of the plan for the day. Then without delay, I got my first task, pinned up on the wall, to add a feature to a django product update script.

They said it wouldn’t be easy, but I said that’s exactly why I’ll take it.

Peter gesturing meaningfully, Simon deeply understanding

Peter gesturing meaningfully, Simon deeply understanding

I spent the rest of the day installing my OS, editors, gaining access to the myriad of systems required to operate, and start learning, fast.

And of course, I had my choice of Kogan “swag” to take home with me on the first day. My choice: a hoodie, and 2 t-shirts.

The rest of the week has been a blast. It’s been very technically challenging, which is exactly why I chose to come here.

I’ve integrated the work required for my first task, and I believe it’s ready to go, but I missed my window by minutes! So close, and yet… It’ll be launched on Monday.

It’s also been personally challenging, because I’ve left so much behind. So many friends. I also need to travel a lot further, which I’m not used to yet. But I’m here late on a Friday because I’m enjoying it; writing this blog to recommend you to join us.

React March Meetup Recap

Last night we hosted the largest-ever React Melbourne Meetup!

Talks included:

  • Short intro to React & Flux - Nick Farrell
  • React migration methods, made easy - Me
  • Converting Stateful components to Redux - Cam Jackson
  • React + Typescript - Basarat Ali Syded

There was a great range of content and complexity across the talks, with some good discussions during the breaks.


I gave an overview of how we have iteratively converted Kogan.com to React, in the most non-breaking way possible.

Slides are embedded below, or available here.

Interested?

If you'd like to learn more about this we are hiring!

Or if you are interested in speaking at the next Meetup, get in touch. You may even get a free hat.

Template Illogic: from Django and Handlebars to React

When the current incarnation of Kogan.com was developed, most pages were rendered server-side with Django templates; client-side rendering used Handlebars.js templates.

As well as the effort (and inevitable mismatching content bugs!) caused by maintaining two templates for some parts of the site, the syntax often became awkward and cumbersome. As Kogan.com expanded with new features and into new markets, both templates grew more complex.

For example, if you wanted a page to use "Zip code" in one country and "Postcode" in the others, one could do this:

     {% if country.name == 'United States' %}  Zip code
     {% else %} Postcode
     {% endif %}

This is just the "obvious" way to choose which word to use; there are more appropriate ways of doing this (using the translation features) as well as less appropriate methods:

     {{ country.name == 'United States'|yesno:"Zip code,Postcode" }}

Unfortunately this isn't so straightforward in Handlebars, as there are no operators to allow comparison of variables - control flow structures such as if/else will only operate directly on the boolean value of a variable.

This is a deliberate design decision by Handlebars to enforce the separation between logic and presentation. Mustache (the templating language Handlebars is based on) is even stricter - there are no explicit control flow statements other than loops.

Therefore, it is necessary to pass a value which can be treated as a boolean to Handlebars:

    {{#if country.isUSA}} Zip code
    {{else}} Postcode
    {{/if}}

Or just take all the logic out of the template and pass the content itself:

    {{ country.prefixOfTermUsedInThisCountryForPostalCodesIncludingSpaceCharacterIfSeparateWord }}code

Handlebars can be extended with arbitrary functions, so some such as #ifEquals have been written to allow equality comparison. Other helpers such as #ifCond can work with any conditional operators, but they have no matching else statement, so it is necessary to use two opposite operators:

    {{#ifCond country.name '==' 'United States'}}
        Zip code
    {{/ifCond}}
    {{#ifCond country.name '!=' 'United States'}}
        Postcode
    {{/ifCond}}

One of the benefits of our migration to React.js is the melding of Javascript with the template system into the creature known as JSX.

Although at first glance this tramples all over the separation of logic and presentation, the logic is restricted to only presentation-related decisions due to the way React enforces rendering from a meticulously defined state.

Keeping the front end code and the template within the same file also means it is easier to figure out what is going on - and much easier for programmers who have less front-end experience to use. Compared to our previous hierarchy of .js and .handlebars files, we have found React is a more readable and maintainable solution for the whole team - most of the time:

    ${storeCode === 'us' ? 'Zip ' : 'Post'}code

How to Transform your Team's Communication with Stakeholders

How to Transform your Team's Communication with Stakeholders

As tech people - we quickly flock to digital tools to manage our workflow and processes.  Although an Agile digital task tracker is a great start - at Kogan.com our Physical wall has had huge impact in communicating work in process and priority to our stakeholders - find out why and how you can transform your stakeholder communication.

eClaire - printing our trello cards for our physical wall

There is nothing that creates visibility, collaboration and flexibility of process in a team like a physical wall.

It allows you to show what is currently being worked on and whois owning it, where the bottlenecks are in your process, how much and what is in the backlog and the list goes on.

It provides an awesome mechanism to trigger and facilitate conversations around and provides a visual que that often speaks a thousand words.

But as much I advocated for physical walls to manage a team they are not without their limitations.

They lack the detail and the reporting aspect that you really need an online tool to fulfill and that's why...

Going too deep with Django Sessions

The other day I was battling with some weird behaviour where a key in a session was updated, but sometimes it would revert after a while.

The key in question was a flag to say that a customer had been sent an email about abandoning their cart, and when the key reverted they ended up getting duplicate emails.  To achieve this, we have an offline Celery task that looks over all sessions in the DB and checks a flag on the cart to know if the email had already been sent.

When I dug into the cases where duplicate emails were sent, I noticed that all of them came back to the site after the first email and started browsing again. But why? Why would browsing the site cause the flag to change state? And why wasn't everyone effected? ...

A hidden gem in Django 1.7: ManifestStaticFilesStorage

The biggest change in Django 1.7 was the built-in schema migration support which everyone is aware of, however 1.7 also shipped with lots of other great additions, ManifestStaticFilesStorage - the new static files storage backend was one of them.

Static file caching is everywhere

Before explaining what ManifestStaticFilesStorage is and how it works, this is the overview of why we need it at Kogan.com:

cache busting static

In order to deliver the content to our customers as fast as possible, we cache the downloaded static files by using max-age request headers. This allows our customers to download the content once and the subsequent requests to static files will be served from a cache. As shown on the diagram, if we were to use normal static file names like base.css, the content of the file would be cached in the CDN as well as on the browser and we would have a hard time trying to invalidate these caches. We cache-bust the content by appending a md5 hash of the content of the file to the file name. When we deploy a new base.css, {% static %} template tag will turn base.css into base.d1833e.css and the browser will then request a new file. {% static %} template tag is able to translate base.css into base.d1833e.css thanks to static files storage backend. This setting is named STATICFILES_STORAGE in Django.

Before ManifestStaticFilesStorage

Our Django app was previously configured to use CachedStaticFilesStorage which resulted in placing file mappings in the CACHES backend, for us it was Redis. Django adds these mappings during collectstatic when it gathers all statics and puts them in one place.

collectstatic

This solution has coupled static assets deployment with code deployment resulting in a number of issues:

  • Running collectstatic as part of code deployment --> slow deploys
  • Extra load on Redis
  • App servers were sometimes out of sync as we deploy them in batch. When we start the deployment, Redis would be updated with the new keys, the first batch of App servers would get the new code, but the other half still had old code.

Out of sync app servers

ManifestStaticFilesStorage to the rescue

ManifestStaticFilesStorage has helped us to decouple the static compilation stage from deployments by allowing Django to read static file mappings from staticfiles.json on a filesystem. staticfiles.json is an artifact file produced by collectstatic with ManifestStaticFilesStorage as a backend. We can now include this staticfiles.json into our code package and deploy it to a single app server without affecting the others.

New ManifestStaticFilesStorage

Where is staticfiles.json located?

By default staticfiles.json will reside in STATIC_ROOT which is the directory where all static files are collected in. We host all our static assets on an S3 bucket which means staticfiles.json by default would end up being synced to S3. However, we wanted it to live in the code directory so we could package it and ship it to each app server. As a result of this, ManifestStaticFilesStorage will look for staticfiles.json in STATIC_ROOT in order to read the mappings. We had to overwrite this behaviour, so we subclassed ManifestStaticFilesStorage:


from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
from django.conf import settings

class KoganManifestStaticFilesStorage(ManifestStaticFilesStorage):

    def read_manifest(self):
        """
        Looks up staticfiles.json in Project directory
        """
        manifest_location = os.path.abspath(
            os.path.join(settings.PROJECT_ROOT, self.manifest_name)
        )
        try:
            with open(manifest_location) as manifest:
                return manifest.read().decode('utf-8')
        except IOError:
            return None

With the above change, Django static template tag will now read the mappings from staticfiles.json that resides in project root directory.

Thanks Django

Thanks to Django 1.7, we've not only gotten a better schema migration system but also improved our deployment process. And not to mention ManifestStaticFilesStorage addition was only 40-50 lines of code (as of the day this blog post was published).

Catches when Expecting Exceptions in Django Unit Tests

To cover all bases when writing a suite of unit tests, you need to test for the exceptional cases. However, handling exceptions can break the usual flow of the test case and confuse Django.

Example scenario: unique_together

For example, we have an ecommerce site with many products serving multiple countries, which may have different national languages. Our products may have a description written in different languages, but only one description per (product, language) pair.

We can set up a unique_together constraint to enforce that unique pairing:

class Description(models.Model):
    product = models.ForeignKey("Product")
    language = models.ForeignKey("countries.Language")

    class Meta:
        unique_together = ("product", "language")

    subtitle = models.CharField(...)
    body = models.CharField(...)
    ...

Developer chooses AssertRaises()

If the unique_together rule is violated, Django will raise an IntegrityError. A unit test can verify that this occurs using assertRaises() on a lambda function:

def test_unique_product_description(self):
   desc1 = DescriptionFactory(self.prod1, self.lang1)
   self.assertRaises(IntegrityError, lambda:
      desc2 = DescriptionFactory(self.prod1, self.lang1)

The assertion passes, but the test will fail with a new exception.

A wild TransactionManagementError appears!

Raising the exception when creating a new object will break the current database transaction, causing further queries to be invalid. The next code that accesses the DB - probably the test teardown - will cause a TransactionManagementError to be thrown:

Traceback (most recent call last):
File ".../test_....py", line 29, in tearDown
   ...
File ...
   ...
File ".../django/db/backends/__init.py, line 386, in validate_no_broken_transaction
An error occurred in the current transaction.
TransactionManagementError: An error occurred in the current transaction.
You can't execute queries until the end of the 'atomic' block.

Developer used transaction.atomic. It's super effective!

Wrapping the test (or just the assertion) in its own transaction will prevent the TransactionManagementError from occurring, as only the inner transaction will be affected by the IntegrityError:

def test_unique_product_description(self):
   desc1 = DescriptionFactory(self.prod1, self.lang1)
   with transaction.atomic():
       self.assertRaises(IntegrityError, lambda:
          desc2 = DescriptionFactory(self.prod1, self.lang1)

You don't have to catch 'em all: Another solution

Another way to fix this issue is to subclass your test from TransactionTestCase instead of the usual TestCase. Despite the name, TransactionTestCase doesn't use DB transactions to reset between tests; instead it truncates the tables. This may make the test slower for some cases, but will be more convenient if you are dealing with many IntegrityErrors in the one test. See the Django Documentation for more details on the difference between the two classes.

Webpack Your Things

We very recently finished migrating our front-end build process to Webpack. As with any reasonably sized codebase, it's always a little more complex than the 3 line examples in how-to guides. This post will list some of the higher-level things I learned during this undertaking, the next one in the Webpack Series will detail some specific quirks and solutions.

Resources

I was largely able to do this by leveraging the hard work of some clever heroes. This very conveniently timed blog post covered a lot of what was needed. JLongster also has some very good tips, helpful for more than just backend apps. Regarding documentation, the widely-cited Webpack How-to gives a pretty concise overview of most things you will need. And of course, the docs have a lot of information. Sometimes too much. But usually most things you need are listed there. Occasionally something isn't, which brings me to lesson one.

Lesson 1: cd node_modules

One of the biggest things I learned in this undertaking isn't limited just to Webpack, and helped fix a few other things. Previously I had treated the node_modules folder as a black box - just npm install and be on my way. This is fine for everyday usage, but when you hit barriers or bugs sometimes you need to do some digging. Rather than throwing random inputs at a black box to measure the effect, you can just crack it open.

A good example of this is the CommonsChunkPlugin, which is documented thusly:

If omitted and options.async or options.children is set all chunks are used, elsewise options.filename is used as chunk name
— chunk.name definition

I found this sentence somewhat confusing, but easy to clarify by reading the code that checks this. If/else and some variable assignments and straightforward things to follow. And the nature of Webpack modules means they are generally quite small, if all else fails just console.log everything.

Note this doesn't necessarily mean you must always open the box and understand the internal implementation of libs you are using. But it is reassuring to know that you can.

Lesson 2: Use Tables

For visualising data, never for layout. The quite excellent webpack analyse tool provides a tonne of useful data to improve your module situation. The crazy tree-view animations look awesome, and are animated and zoomable. But they can quickly spiral out of control into a meaningless ball of branches. There are fortunately table views for all these pages as well. While repeatedly processing a bunch of lists to try minimising file sizes isn't the most romantic task, you can sort and group a lot more easily. Conversely, the Chunks tree view stays parseable for a longer time (as you will have fewer chunks than modules). It can give a quick overview if any of your chunks are ballooning out of control, and the accompanying table used for more automated analysis.

Trees: Awesome, albeit unclear

Lesson 3: Measuring Victory

As with any code change, the best measure of success is not breaking anything. In this case our ideal outcome was the change was completely invisible to end users, assuming everything deploys and the site still works. Beyond that it was meant to simplify frontend development for all of our devs, again a success. We have simple build & watch tasks without global Node package requirements (except npm). We were able to deploy our first React component by adding a single line to process JSX. So we don't (yet) have a quantifiable metric for the success of this adventure into the world of Webpack. But as someone who formerly complained about writing build tasks, it has been fun.


In the next post I will drill down into a few specific issues encountered and how they were fixed, and some other useful features and tricks. If you have any advice or thoughts, we'd love to work with you - careers.