portrait picture

TIMO ZIMMERMANN

balancing software engineering & infosec

Managing state in Django models

posted on Friday 20th of March 2020 in

A Django model often will contain some form of state. And there will most likely be events which modify the state. While those two things are not always called like this, the concept still exists. More often than not you will find a view controller assigning the new state to an instance of a model when certain conditions are met. In some cases you will see the event code being abstracted to make it easier to test, maybe in a separate function or class, maybe to a model method. And more often than you should you will see this messing up the state of an instance forcing someone to wake up at 2am to connect to the database – or Django admin if setup – and set a valid state. Thankfully the chance for the last thing to happen can be greatly reduced.

Over the years I found that not everyone is familiar with the concept of a finite-state machine (which would help addressing this problem). I have also met people who had in depth knowledge of state machines in all their forms and would have been able to implement one as a stand-alone system, but had trouble integrating one with a Django model. In this article we will focus on the practical application of having centralised logic to control state and events transitioning the state of an object.

This article assumes you have basic knowledge of Django and Python and are familiar with the way Django’s models work.
You can find the code used in this demo here.

Traffic Lights

Let us start with the most simplified example I can think of – traffic lights. A traffic light has three different states: red, yellow and green. There are three possible transitions between those states:

The order in which those transitions happen is – during regular operation – always the same. While traffic lights are actually a lot harder in practice, we simply assume we have have a script changing the state in regular intervals. To do this the script fetches the traffic light entry from the database and calls a transition method. Since there is only one possible state to transition we do not need to handle multiple events.

# coding: utf-8
from django.db import models


STATE_RED = "stop"
STATE_YELLOW = "caution"
STATE_GREEN = "gogogo"

STATE_CHOICES = (
    (STATE_RED, STATE_RED),
    (STATE_YELLOW, STATE_YELLOW),
    (STATE_GREEN, STATE_GREEN),
)

TRANSITIONS = {
    STATE_RED: STATE_GREEN,
    STATE_YELLOW: STATE_RED,
    STATE_GREEN: STATE_YELLOW,
}


class TrafficLight(models.Model):
    """to change state of the traffic light call `TrafficLight().transition()`
    """

    # keep state when initalising the model to avoid additional DB lookups
    __current_state = None

    state = models.CharField(
        max_length=20, choices=STATE_CHOICES, default=STATE_RED
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__current_state = self.state

    def save(
        self,
        force_insert=False,
        force_update=False,
        using=None,
        update_fields=None,
    ):
        allowed_next = TRANSITIONS[self.__current_state]

        # skip validation if the model is being created
        updated = self.state != self.__current_state
        if self.pk and updated and allowed_next != self.state:
            raise Exception("Invalid transition.", self.state, allowed_next)

        # manually set __current_state to ensure instances can be used mutliple
        # times without running into validation errors
        if self.pk and updated:
            self.__current_state = allowed_next

        return super().save(
            force_insert=force_insert,
            force_update=force_update,
            using=using,
            update_fields=update_fields,
        )

    def transition(self):
        next_state = TRANSITIONS[self.state]
        self.state = next_state
        self.save()

There are a few things going on here and, I would assume, one thing that might look a bit different than what some might expect from a regular Django model. If you have never overwritten a models save() method I would advise reading up on the various arguments and what they mean. They might be useful some day.

First of all we overwrite __init__() to keep the current state of the traffic light around. By doing so we can avoid an additional database call in save() where otherwise we would have to fetch the instance we are operating on to compare the new value to the existing one.

Next, in save() we actually ensure that a transition from the current state to the new state is valid.

We update __current_state so next time we call transition() the instance actually knows about the current state of the traffic light without refreshing from the database.

Using a shell session we can validate everything is working as expected.

>>> from traffic.models import TrafficLight
>>> t = TrafficLight.objects.create()
>>> t.state
'stop'
>>> t.transition()
>>> t.state
'gogogo'
>>> t.transition()
>>> t.state
'caution'
>>> t.transition()
>>> t.state
'stop'
>>> t2 = TrafficLight.objects.get(pk=t.pk)
>>> t2.state
'stop'
>>>

But what happens when we manually try to set an invalid state?

>>> from traffic.models import TrafficLight, STATE_RED
>>> t = TrafficLight.objects.last()
>>> t.state
'gogogo'
>>> t.state = STATE_RED
>>> t.save()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File ".../django-state-machine/traffic/models.py", line 50, in save
    raise Exception("Invalid transition.", self.state, allowed_next)
Exception: ('Invalid transition.', 'stop', 'caution')
>>>

Our save() method properly throws an exception. Perfect, so we guarded against the most common ways to set an illegal state. Someone can obviously still run raw SQL queries via django.db.connection, but once we take this into consideration we would also have to account for direct connections to the database for example, which is out of scope for this article.

Airport Pickup

Now that we have seen some a basic, functioning example let us dive into a slightly more complex one.

Let us write a small piece of software for a hotel which offers an airport shuttle service for their VIP customers. A standard pickup of a customer arriving at an airport would look like this:

Now there are obviously a few things which might need a different transition than the ones outlined in the flow above – like a customer who needs to be transported to the airport or a driver declining a request at any stage before picking up a customer which then needs to be re-assigned. We will not cover all possible scenarios, just enough to demonstrate the concept of a model reacting to events which determine the new state.

# coding: utf-8
from django.db import models


STATE_REQUEST = "request"
STATE_WAITING = "waiting"
STATE_TO_AIRPORT = "to_airport"
STATE_TO_HOTEL = "to_hotel"
STATE_DROPPED_OFF = "dropped_off"

STATE_CHOICES = (
    (STATE_REQUEST, STATE_REQUEST),
    (STATE_WAITING, STATE_WAITING),
    (STATE_TO_AIRPORT, STATE_TO_AIRPORT),
    (STATE_TO_HOTEL, STATE_TO_HOTEL),
    (STATE_DROPPED_OFF, STATE_DROPPED_OFF),
)

TRANSITIONS = {
    STATE_REQUEST: [STATE_WAITING,],
    STATE_WAITING: [STATE_REQUEST, STATE_TO_AIRPORT],
    STATE_TO_AIRPORT: [STATE_TO_HOTEL, STATE_REQUEST],
    STATE_TO_HOTEL: [STATE_DROPPED_OFF],
    STATE_DROPPED_OFF: [],
}


class Pickup(models.Model):
    __current_state = None

    state = models.CharField(
        max_length=20, choices=STATE_CHOICES, default=STATE_REQUEST
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__current_state = self.state

    def save(
        self,
        force_insert=False,
        force_update=False,
        using=None,
        update_fields=None,
    ):
        allowed_next = TRANSITIONS[self.__current_state]

        updated = self.state != self.__current_state
        if self.pk and updated and self.state not in allowed_next:
            raise Exception("Invalid transition.", self.state, allowed_next)

        if self.pk and updated:
            self.__current_state = self.state

        return super().save(
            force_insert=force_insert,
            force_update=force_update,
            using=using,
            update_fields=update_fields,
        )

    def _transition(self, state):
        self.state = state
        self.save()

    def assign(self, driver):
        # we omit storing the driver on the model for simplicity of the example
        self._transition(STATE_WAITING)

    def accept(self):
        self._transition(STATE_TO_AIRPORT)

    def decline(self):
        self._transition(STATE_REQUEST)

    def picked_up(self):
        self._transition(STATE_TO_HOTEL)

    def dropped_off(self):
        self._transition(STATE_DROPPED_OFF)

Instead of calling transition() we call an event like picked_up() or decline(). While the methods are only calling the _transition() method and passing in the state to transition to, in a real application we would likely trigger additional functionality like sending an SMS to let a driver know a request was assigned to them or letting the hotel staff know that a customer was just dropped off so they can start the sign in procedure.

A short test run demonstrates that the transitions work as expected.

>>> from pickup.models import Pickup
>>> p = Pickup.objects.create()
>>> p.state
'request'
>>> p.assign("driver1")
>>> p.state
'waiting'
>>> p.decline()
>>> p.state
'request'
>>> p.assign("driver2")
>>> p.state
'waiting'
>>> p.accept()
>>> p.state
'to_airport'
>>> p.picked_up()
>>> p.state
'to_hotel'
>>> p.dropped_off()
>>> p.state
'dropped_off'
>>>

State assignments

You might now rightfully argue that things can still go wrong, especially if additional functionality should be triggered as part of an event. What if someone sets a pickup to dropped_off which was in the to_hotel state? It is a valid transition, but hotel staff would never be notified.

You can obviously make this part of your code reviews, but as your codebase and team grows this practice will fail you at some point. Not specifically for assigning state, but for basically everything. People miss things, that is just a matter of fact.

If you reached the point at which you want to add additional safe guards you can always extend your model with more validators or add linter rules. One approach might be checking the caller of the _transition() method ensuring they are whitelisted.

import inspect


class Foo:
    def transition(self):
        curframe = inspect.currentframe()
        calframe = inspect.getouterframes(curframe, 2)

        if calframe[1][3] != "caller1":
            raise Exception("nope")

    def caller1(self):
        self.transition()


def caller2(foo):
    foo.transition()


f = Foo()
print("caller1")
f.caller1()
print("caller2")
caller2(f)

Calling transition() from caller1() is allowed while caller2() throws an exception.

>>> import test
caller1
caller2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../django-state-machine/pickup/test.py", line 24, in <module>
    caller2(f)
  File ".../django-state-machine/pickup/test.py", line 17, in caller2
    foo.transition()
  File ".../django-state-machine/pickup/test.py", line 10, in transition
    raise Exception("nope")
Exception: nope

While many Django devs prefer fat models I would argue that with enough additional validation you will blow up the size of the model significantly and should externalise the whole state transitions and validation code and pass your model in. This usually results in a design which is easier to test and reason about.

Alternatives approaches and pitfalls

An alternative approach would be a pre_save() receiver instead of overwriting save(). While signals sound like a good idea, in practice they have shown to simply be a burden during the maintenance phase. This does not mean there is no time or place to use signals, but they are far rarer than some codebases make you believe.

You want to be very careful how you trigger events. For more complex examples relying on the current state of an instance you usually do not want to call events and transitions in parallel, especially when the outcome of a transition is communicated back to a client which changes state based on the outcome of the transition. Some form of locking or a FIFO queue might be sufficient to solve this problem sufficiently well, but at the end of the day it really depends on your specific use case.

Libraries

I want to mention two libraries which have proven to be quite good and might come in handy if you do not have a Django model to work with, do not want to build everything yourself or simply want to study possible implementations – transitions and state_machine.

Conclusion

There is some overhead involved in building proper state management into your model and it might require some comments in code or a small document outlining how exactly it works (so new hires can catch up with the design of the system). The examples also might seem a bit dull and solvable with less code, but past experience shows that as you scale your application and solve harder problems than a traffic light’s proper state management is something worth building. Especially in business critical paths of your code where a wrong state might result in customers receiving a sub-par service, you losing money or worst case the system ending up in a state which cannot be recovered and blocks users from interacting with it.


Working from home – things no one talks about

posted on Saturday 14th of March 2020 in , ,

With COVID-19 spreading more and more people will find themselves working from home over the next few weeks, and from what I can tell there is no real understanding when we will resume office hours as usual. This obviously is a challenge for people who never worked remotely and organisations who are not prepared for having all (or large parts) of their workforce remote or working from home. Once all of this is over companies will realise that some people can work effectively from anywhere or and some absolutely need an office environment to be productive.

I have seen quite a lot articles and discussions with advice on how to work remotely. Most of them are stating the obvious things like “get a proper screen and keyboard” or “get yourself a nice chair” – valuable advice you should follow when you are expected to work from home for an unknown amount of time. Get proper gear, your body will thank you. But there were some things I have not seen discussed a lot or at all which I – not having worked (sometimes visited) in an office in 20 years – consider worth knowing. Especially since they seem as obvious as getting a nice chair to me.

When talking about advice there are a three things to keep in mind:

  1. If an advice works for you depends solely on you. What works for me does not necessarily work for you – YMMV.
  2. Anything can be controversial if the crowd discussing a specific topic is large enough. Pick what makes sense to you and go with it.
  3. As everything in this blog the target audience is usually found working on a computer. Sorry, I do not have good advice on how to sell shoes remotely.

Everyone is talking about chairs and monitors. But please, for the sake of everyone on the call, get proper headphones. It does not have to be fancy, but eliminating background and potential typing noise for you and everyone on the call goes a long way to make the whole (video) conference experience more pleasant. You got AirPods? Great! You want to go all out fancy with Bose 700? Cool. Just want a solid pair for conferencing? Sennheiser HD 4.50 got your back.

If your company got video conferencing and you do not have an integrated webcam, then get one. The Logitech Brio is amazing – I would suggest dropping down to FullHD 60fps instead of 4k 30fps. Also a great one, but a lot cheaper, is the Logitech C920S. This will give you some perceived form of face time with your colleagues, read their reactions and helps people a little bit who feel isolated, alone or cut off. Turn on your video whenever you can.

While on the subject on people feeling isolated I would suggest setting up a channel in your company chat to just talk. Use it to talk about everything you would during your lunch break (or at the infamous water cooler). At first it might feel a bit awkward and for most people it will surely not be the same, but every little bit will help.

I would expect employers to reimburse expenses required to effectively work from home. But as many things I would expect to be true this might not be the case. The cost for a few items to make working from home healthy and an overall better experience are relatively small compared to many engineering salaries. If you have the financial means just get them. Consider it part of being a professional to deliver the best possible performance no matter the circumstances. Your boss may not send you a thank you card, the company may choose to ignore it, but your colleagues will most likely remember.

While talking about video conferencing, things will be less professional. Live with it and do not make a fuss about it every single time something happens. Remember, people may be forced to work from home. There will be kids, animals and other distracting things that are cute, strange, hilarious or all together. Get over it. If a cute dog appears on video a private message in your chat is most likely appreciated by everyone. Comments about a child eating a stuffed animal not so much. But please do not share your enthusiasm for the cat that looks like Garfield with everyone on the call.

I would also suggest doing one or two test calls with a colleague to make sure video and audio are working. It is 2020. Video conferencing is still not that good, but still: The first five minutes of a call should not consist of “CAN YOU HEAR ME NOOOOOOOOOOWWWWWWWWWW”.

One thing I have seen a lot of people miss over the years is their environment. The part of your home you are sharing on screen is now part of your work environment. I would suggest making sure it looks like that. Adult themed photos or pictures, toys you usually only share with someone special or items used for recreational activities should not appear in the background. Some of you may now think I am joking, but believe me, I am not.

Despite having seen the next advice more often I still want to add it here as I believe it is are critical for anyone transitioning from full time office to working from home.

Make sure you have a fixed schedule. Get up. Get ready. Do your job. Sign off. You may easily get lost playing a game, doing some chores, playing with the cat or walking the dog. You get some more time since you do not have to commute, use it wisely. A proper schedule is tremendously useful, especially when just making the transition.

If you are uncertain how to properly make sure you get a healthy lunch and dinner check “Fitness YouTube”. There are tons of videos around meal prep. You like Burritos and could eat them every day? Cool. Healthy Burritos for a whole week take half an hour to an hour to prepare and 5 minutes to warm up. You will surely find a recipe you like and thanks to the fitness and bodybuilding community the nutritional value is usually pretty good.

Make sure to get breaks. Being lost in work can happen and it happens a lot easier when you do not leave the office. I actually meant it when I said “sign off” a few sentences ago. Burning out during your work from home time will neither help you nor your company – with the later being the less relevant part in this sentence.

The whole situation is obviously far from optimal. Let us try to make the best out of it!


What is better than one HomePod?

posted on Saturday 7th of March 2020 in ,

You might have guessed correctly – two HomePods. I usually refrain from posting short reviews of tech, but I am making an exception here, because having tried to find information and recommendations about HomePods in a stereo setup is not that easy and having used them for two weeks now I can say for sure they are under- and overrated at the same time.

stereo HomePods (and a screen I still didn’t wall mount…)

First of all let us define for whom this review is meant. If you believe you can hear if a twenty centimeter long cable between your pre-amp and amp got some gold coating – then HomePods (and this review) are not for you. If you are a hardcore gamer who could not stand a moment without an RGB mousepad synced with your mouse then this also not for you. For the rest of us: let‘s talk.

If you know how one HomePod sounds, you still have no idea how two of them sound. It just gets „better“. By a large margin. Especially the bass. If you like the general sound of one, there is a good chance you will enjoy a stereo pair. And let us be honest for a moment: watching a movie with mono sound is just unenjoyable.

But this does not mean there are no alternatives. A pair of Klipsch R-41M paired with a Yamaha R-S202 will likely be a bit cheaper, except when you find one of the really good deals on HomePods, then they might come out around the same total. With HomePods you will have an easier setup and a good integration with the Apple ecosystem. With an amp and speakers you are far more flexible and can easily add a few dollars here and there and just get a lot more sound or upgrade at a later point in time.

HomePods play nicely with Apple devices, except your Mac where you cannot set the stereo pair as standard output and keep it that way. Which surely makes sense for someone at Apple, just not anyone I talked to who owns a Mac and two HomePods.

To answer the big question: Should you get two? If you are only or mostly on Apple hardware, want really good sound, the least amount of cables, not have a lot space to spare and two HomePods are within the budget you want to spend: Yes, definitely. If you want to optimize for „the perfect sound“, stay flexible or prefer to spend a little bit less for a setup that still sounds good? Likely not.


Security 101: OWASP

posted on Tuesday 3rd of March 2020 in ,

One of the questions I am often asked when offering office hours for startups as a part of SpinLab is how they can step up their security game without hiring additional people or bringing in a consultant. I would love to have a silver bullet that magically secures your system for free. I really do. But in the absence of magic we have to work with the tools at hand, one of them being the OWASP Top 10.

OWASP – the Open Web Application Security Project – offers tons of resources, but the one most people are likely aware of is the OWASP Top 10. It is a collection of the ten most critical risks for web applications. And best of all? It is being kept up to date – which makes it even more sad that some of these risks are still on the list considering we are dealing with them for over… what, two decades?

But this is not all! Assuming you take the time to read through the top 10 list you’ll see “A10-Insufficient Logging & Monitoring” with a two sentence outline giving you a summary of the problem. What exactly does this mean? How can you prevent it? What is the risk? Easy. Click the link and read a lot more about it. It will actually answer all those questions.

Okay, now you know the full extend of the risk and you have a basic understanding of how to mitigate it. But where can you get some more information and examples? OWASP Cheatsheet Series to the rescue! Another resource provided by OWASP is an amazing collection of cheatsheets with enough info to keep you busy for days and weeks. Hopefully not months, that would be bad for an early stage startup. I would actually recommend reading as many cheatsheets as you can and internalising the information. Most of them will come in handy at some point.

Two additional resources which are part of the Top 10 you should read are the What’s Next For Developers and What’s Next for Organisations. This will likely be the point where you decide that you do not have the time to work through all of the advice you find in there and have to do it at a later date. Which is fine. What those two resources provide you with right now is the knowledge that there is a lot more work waiting for you and it hopefully acts as a reminder to constantly work on the security aspects of your application.

OWASP provides lots of great resources. It is out there, it is free to use. It requires some time, but it is in my opinion approachable for any software engineer, no matter how much prior exposure to the security field they had.

Engineering time is usually a constraining factor for an early stage startup, nearly as much as capital. So it might feel counter intuitive to ask engineers to spend time on learning more about security instead of letting them write code and bringing in external resources to help you out with securing your system. But let me tell you an open secret: many engagements start with establishing basics and testing for vulnerabilities covered by OWASP.

If your team is already well versed in the basics you do not only reduce the constantly existing risk of new vulnerabilities being introduced into your system. When you decide to bring in a third party the basics and training portion is often completed a lot faster allowing your consultant and engineers to spend the remaining time working on the finer details, alternative approaches, different thread models,… that often have to be ignored when there is a hard limit on time and / or budget. Or, for the business savvy readers of this post: Your ROI will be significantly higher.

If you want to follow this article series you can either subscribe to the general RSS feed or to the tag specific one if you only care about startup security posts.


Software development on an iPad Pro

posted on Thursday 30th of January 2020 in , ,

Looking back at all the work I did over the last decade one thing was mostly constant: how my system was setup, which tools you could find on it and how I worked on projects. But things changed a little bit when I decided to do more work on my iPad. When traveling I usually do not write a lot of code, if any at all. But incidents happen or sometimes there is simply enough downtime to get some coding done between meetings, mentoring and architecture sessions. When traveling I usually focus on those three things and the iPad is the perfect device for me for all three of them. But writing code? I usually had a MacBook with me for that.

If your first thought reading this article is “everything comes around, he is talking about thin clients” you are not that far off. But first let us take a step back and talk about the things I did not particularly like about my old setup.

Dependency management. Jumping between legacy projects and greenfield / explorative projects usually means different versions of a compiler or interpreter, different versions of libraries, sometimes incompatible with each other and service dependencies in various versions like databases or key value stores. There are some solutions to this problem, but none that are mostly seamless and pain free to use. And even after those problems are solved you might not be able to run the production version of a dependency on your laptop which means you either end up with some form of Docker or VM setup or you yolo through development and hope the CI does its job well.

Reliance on one system. I obviously backup data, configs and usually the whole system. And document how to setup a project. While this might not be the case for every project you will ever touch, it should be. But what happens when your system is damaged or stolen while you are traveling? You likely buy a new one and start setting up everything as good as you can – maybe you are lucky and got a backup with you. If you do not trust your own hardware you can provision an instance hosted by one of the larger cloud providers.

Keeping systems in sync. You would expect this to be a solved problem considering all the options to synchronise files across multiple devices. But the moment you add caching and database systems, maybe an S3 compatible server to test against the actual API things get a lot trickier. I usually synced my MacBook, which was unused for weeks somewhere on a shelve, a few days before traveling. You read that right, I had a MacBook which I touched three to four times a year. I do not work on the couch or in the living room. I might read a bit, pull up a code review, maybe draw an architecture diagram from time to time, but I do not sit on the couch coding. When I write code I am in my office in front of my iMac. With the screen positioned at a suitable high, on a proper chair with all the bells and whistles to make it as comfortable and ergonomic as it can be.

When macOS Catalina was released I did a clean installation on my iMac Pro and promised myself to not install project related software or dependencies on it anymore.

So I started out with the most obvious solution. PyCharm with a remote interpreter setup. With one VM powered by Parallels for each project running the Linux distribution and dependencies mirroring production. Now some might be curious why I opted for Parallels and not Docker. I am more comfortable using a real VM, it is way easier for me to configure, work with, snapshot – including the filesystem – and resource usage was roughly the same. Parallels is shockingly good at managing resources. Also I am working on a way to powerful iMac Pro for simply putting text in a file, resources are plenty and if they were not it would be cheaper to acquire more resources than spend my time on bending Docker to my will.

This worked nicely for a side project and the Node.js stack we are using at Nurx. But it tied me to my iMac, which was not optimal considering that I will surely not ship it to where I travel. So I had to figure out how to write code on my iPad.

One solution many people figured out before me is pretty simple, especially when you wrote code while the year started with a 1. Vim. Well, a slightly newer fork – NeoVim. Together with CoC which brings the Language Server Protocol to NeoVim it seemed like a pretty good choice. And it is. Together with Blink the experience is actually really nice. A few days in I was starting to remember why I did not like vim when more complex plugins were being used – debugging why it stopped working is a nightmare. I already see the die hard vim fans lining up to tell me this either never happens or that I should not be using plugins at all – good points, thanks for the input, moving on.

I started to explore browser based IDEs. I knew about Eclipse Che and if this would be the answer I would be ordering a 16″ MacBook Pro right now. Other options were worse in some aspects. But someone, somewhere did something smart. They took a desktop editor which can be bloated with plugins to behave like an IDE build with web technology and made it possible to run it as daemon and connect to it via a browser. code-server brings VSCode to the browser. In nearly all its glory. And it actually works really well.

Here is the summary on how you use it:

  1. download latest version
  2. start it and point it to your project directory
  3. there is no step three

It is simple. It is powerful. It is well supported with a ton of plugins. And it is actively being maintained. Even Microsoft is working on making VSCode viable from anywhere. The integrated terminal means I do not have to run two apps side by side on the iPad to work on code, but can simply keep a browser open.

This seems like a pretty decent setup to work with. But it still forced me to keep two systems in sync. I tried using code-server as main development environment for a bit, but constantly switching to the correct browser tab or running a different browser for easy access was a bit annoying.

The good news is that Visual Studio Code with its remote capabilities solves all of this. I can connect to the exact same box code-server is running on and once settings sync is setup keeping the editor configuration the same should be fairly straight forward.

Moving to Visual Studio Code was nearly painless. It does not really use much more resources than a Java based IDE – what a benchmark, I know. IntelliSense works really well except for Djangos models which, thanks to an obscene amount of meta programming, always causes some problems for completion providers and I am still impressed how well JetBrains solved this problem. Debugging is not implemented as nicely as you are used to by some IDEs, but more than functional enough and it is improving. Overall I am happy with it. I always eye NeoVim and think it would make things a bit easier, but somehow the VSC setup feels good, so “meh”.

Let us talk about accessories for a bit. You want a keyboard and a mouse. Thanks to iPad OS you can use a mouse and for the sake of comfort and usability you want one. The cursor is stupidly large, I hope they will introduce an option to make it more like a regular one at some point. Other than that it works really well. Except when you want to use a Magic Mouse which needs like five additional steps to pair and is horrible to use – makes sense, right? I chose to get another Logitech MX Ergo, I really like trackballs and this one is amazing. As keyboard I chose a Magic Keyboard. Works really well, it is light and easier to transport. I tried the Smart Folio Keyboard for the iPad, but I neither like the tilt when the iPad is standing in it nor the feel of the keys. As a stand I am using the standard Smart Folio which I like from a viewing angle and can easily put it on a box or some books to elevate it when I feel like it. Luckily all three choices are easily replaceable with whatever you prefer.

There is another reason I like this approach, even if it was not the driving factor. Cost. A decent server for the basement in my basement, regularly updated is cheaper – as in total cost of ownership – than an iMac Pro or Mac Pro. I do not believe Apples hardware is overpriced, it provides a lot more than what the server needs in this case. The server needs CPUs, memory and storage. Fast and a lot. I currently run 40 cores with 256GB memory, an SSD raid and one NVMe. This is is fast. It has more resources than most people need. I can run a whole production stack of some startups from memory, including all the data. If you do not want to host hardware some instance somewhere in the cloud might do the trick and with only running it while you actually work this can be relatively cheap.

Now comes the tricky part – what would I use as desktop? I could actually use the server itself. I could connect my iPad to an external screen, but sadly this messes with the resolution and keeps the iPads aspect ratio which is annoying to look at and I would prefer some window management capabilities for more than two apps for my primary driver, even when I primarily work with one or two windows on screenSamsung DeX handles this pretty nicely. I could use an Asus ChromeBit. Maybe Apple comes around and fixes all annoyances when connecting an iPad to a larger screen.

As long as I have to occasionally work on iOS and Android projects my iMac Pro is here to stay. The “iPad coding setup” works extremely well for web development and some data / reporting / analytics related projects – the Jupyter client Juno Connect is amazing – but I currently do not see any nice way to get full desktop access on a remote system. This includes VNC, Jump Desktop and RDP. Believe me, I tried.

So how did this setup treat me so far? Excellent. I have been using it for over three months now. I spent roughly 10 days working only with the iPad which equals one long business trip. I never felt limited in any way and even tried to put more time in coding to see if I can find any problems when using it for longer periods than I expect my regular use to be. In the long run I might consider provisioning a VPS before traveling (maybe I can setup a LXC sync with proxmox – which is currently powering my VMs) just in case something happens at home and my VPN is down or the server is literally on fire. I would prefer to not have to re-provision my environment during a trip, which I could since all configs are in git and accessible to me, but this would also require VPN access to my home network, so the single point of failure is the same. Since most work is happening in the browser latency was not really an issue. Can I recommend this setup to everyone? The answer is clearly “no”. But I can say it is more than functional and might work well for you if you give it a try.