For the last few years I’ve been a part of several brand new and phase one projects. No matter the size of the team or business this project has begun with, mistakes are going to be made. Here I’m going to outline some of the most impactful decisions and their consequences I’ve come across. Follow all these steps closely to run a truly terrible project.
1. Bootstrap your spiked work into production ready code.
“Oh cool I got it working, let’s use it how it is.” — Dev who just finished configuring webpack for the first time.
Understand the magic of what you discovered.
Generally at the start of a new project, there is some time put aside to figure out your stack and approach. Whether this is a totally new set of systems or a single new layer, you will find some form of spiking is done. An example would be an API server with a new library you haven’t used before. The first step would be getting it running with a nice ‘Hello world’ response, then maybe you’ll rope in some middleware to deal with authentication. At this point you decide, ok, cool lets commit that to master and we’re good to go, right?
Wrong. It’s at this point you need to really understand what you’ve done and why/how it’s working.
“Why do we have to do this in such a weird way? I dunno that’s just how I worked it out when we were looking at this at the start. Can we fix it? Nah we don’t have time to refactor our entire state model right now.” — Dev with a 3000 line state file
What you need to do is take that progress you’ve made as an example and re-implement it from scratch. This solves some fundamental problems with using your spike code as your foundation: In many cases there’s going to be artefacts, hacks and misunderstandings hanging around in your project that are going to be there for a very, very long time. You can then use this process as an opportunity to take your team through your discoveries and share that knowledge around.
This is also applicable to adding any sort of new complicated layer to your systems. Whether it be right at the start or 8 years into your projects history, you don’t want these spikes immediately creating tech debt. Is that thousand line pull request going to get the understanding it should? Probably not.
2. Don’t bother adding in those nice-to-haves. Testing, Monitoring, Infrastructure as code, Deployments etc. You can do that later.
Testing and Monitoring
“We just don’t have time to be writing tests right now.” — Dev who just broke 8 other pages with a bug-fix
Depending on where you work, the attitude towards testing and monitoring is going to be one of either extreme necessity or indifference. One of the biggest indicators towards a healthy, robust system is the presence of these two pieces.
When it comes to testing, the initial implementation can be somewhat daunting: Mocking external services and layers can be a pain and with larger, established systems it almost always is. Which is why you need to start now, before your code moves past the ‘hello world’ phase a testing framework needs to be established. Turn your ‘hello world’ endpoint/component into a health-check, add a test to hit that endpoint, move the health-check logic into a helper layer and unit test to cover it. Boom you now have service and unit tests. If it’s a Frontend system add a health-check route that you can render and check for. This should be your bare minimum. Try to ensure that each new endpoint/route/component has at least a single green test. This feels like a lot of work, but seriously it isn’t. At the most you might be adding 10% overhead to your features but the cost is 100% worth it.
As you add more complexity to your system you can now bite off those painful areas one at a time, rather than trying to mock database, payment, geocode, chatbot, rocket diagnostics integrations all at once.
Your tests should be an evolving part of your system, adding bits here and there as it grows and as you add more tests. You shouldn’t be writing your testing process from scratch every time you add a new test. The level of coverage you require for your tests is always going to be different but I’d recommend at least green path on all your code. This doesn’t mean a unit test for every method. If your new functionality uses a bunch of private methods, test the entry point you do have access too. Also, don’t be afraid to make complex methods public if you do think they need more focus with testing.
The polar opposite if this is testing to the point of frivolity. You don’t need to test that number variable you defined cant be assigned a string. Don’t write unit tests that try and test all the internals of a third party library you used. What you’ve chosen should already be tested by the distributor and if isn’t, choose something else.
Integration tests can come a little later when you figure out your build and deployment process, but as a jumping off point you should be sorted by now.
“If it goes down we’ll know about it anyway. Hang on, why is that build red?” — Dev who just realised their system hasn’t deployed for 3 days
The integrations for monitoring these days are more seamless than ever. Whether it be bespoke open-source implementations you roll yourself or third party integrations that you’re paying for, they are usually almost as simple as dropping in a single library and hooking up a key to use. Look at the options and decide what you need. A simple implementation of each one really shouldn’t take more than a couple hours. At the very least up/down monitoring should be used and there are plenty of even free options to choose from.
Don’t be afraid to integrate these services into your communication networks either. Slack integrations are usually painless to add and well documented, even Microsoft Teams isn’t too bad when it comes to monitoring alerts.
Infrastructure as Code (IAC) and Continuous deployment (CD)
“It takes too long to write all that, i’ll just upload this folder into AWS for now” — Dev who spends an hour every day manually deploying new releases
In many cases, as part of the new project you are also tasked with getting your service deployed. A stepping stone on the way to this is usually spinning up an instance manually and uploading your service onto it, just to prove that it runs. This is fine for your SPIKE part of the project, but as you move past this stage, it should be automated. Unless you are using an in-house hosting system, your third party provider should support this. With the prevalence of businesses using these services, the documentation is usually all you need to get yourself started and for most simple systems it shouldn’t be too difficult to figure out what kind of basic hosting you’re going to need.
Continuous deployment (CD) is a scary phrase for a lot of established businesses and the industry is still very used to planned release cycles (you may have contractual obligations in place that prevent you from using CD, so there may be no choice in this). Adding in your build pipeline to be automatically triggered each day/commit/release number, early on in your project is only going to have benefits. It’s going to need to be done at some point and there’s no better time than right at the beginning before the complexity piles up.
3. Avoid setting up a development environment.
“Make everyone run the entire stack locally.” — Dev who’s about to write the biggest README you’ve ever seen… that stops being accurate in three weeks
I heavily recommend a ‘dev’ stack of your whole system to be spun up beside your production stack and yes, this does add a small cost to the hosting bill at the end of the month. What do you get out of this?
To begin with, you have an entire stack to run integration tests on. If you factor this deployment into your pipeline before your production stack is deployed, there’s nothing stopping you from being able to run a myriad of suites against it to ensure nothing is wrong with your current release.
You can point any part of your development environment at any layer you want. Do your frontend devs really need to be running that Go server and Mongodb locally? Could they just point their frontend at the service you already have hosted? Being able to offset the entry-point load your devs have will immediately be reducing headache potential.
4. Pick as many brown/greenfield technologies to use as you can.
“Let’s be cutting edge and use the newest of the newest! Eventually everything will be compatible!” — Dev who’s going to spend the next 3 months troubleshooting every library they’ve used
As a preface, I strongly suggest using the latest of any library you are implementing for the first time (as long as the rest of the system supports it). Code rot and library debt adds up very quickly.
There’s nothing wrong with using emerging tech. But you do need to pick your battles and choose what’s appropriate for your situation. It’s very tempting to use ‘the next big thing’ but there are some questions you need to ask before committing all the way.
- Do you have enough time to figure out the kinks that you might come across?
- Do you have the time to update your libraries when new versions/features/breaking changes come out?
- Are breaking changes coming out often? Will related libraries need to be updated to support them?
- What stage is the tech in? Is it alpha/beta? Is it going to go to full release?
- What is the community around this tech like? Do they have a lot of open PR’s? Do they have a lot of open issues? Do they reply in a timely manner? What does their roadmap look like? Do they have all the features you need right now?
While at the start of your project you may have plenty of time to spike out and implement every new bit of tech you want to use, the pain is going to come in the support of and edge cases you find while using your new tech. Ensuring you have all the resources you need and needed updates readily flowing is going to heavily affect the future support of this project.
5. Literally build the entire project as your MVP
“Let’s just call our MVP the first customer ready release.” — Project manager about to set an ridiculous goal
Seriously, you need to focus on what your MVP (Minimum viable product) is and stick to it. Too often I see MVPs being stretched and morphed into more feature-heavy pixel-perfect examples. Scope creep is a massive issue and almost no project is safe. Does it really matter that that box is slightly offset from the design? Do you really need concurrent usage to prove that product editing page will satisfy one of your workflows? Does it really matter that an eventual event confirmation takes a little bit longer than you anticipated? At some point, yes they will matter, but when it comes to your MVP, probably not.
MVPs need to be built to prove your stack and your core workflows work. MVPs are tricky to define. It’s really up to your exact use case as to what they entail, but for me a general rule of thumb is: The first time your product satisfies all of your core workflows. In something like an online store it may be: Being able to create an account, a product, post that product and have that product purchased. With a smaller scope it may just be an event consumer that then produces another event. Whatever it is, you need to stick to that scope and make sure it’s built first.
One of my biggest gripes with trying to reach this stage is adhering perfectly to a UI design, this is definitely more of an issue when it comes to frontend development. There needs to be a clear understanding within the business and within your team as to what stage of development is going to focus on perfecting your UI. Saving your changes to the API > An input field being the right length.
6. Design your UI/API/Features/Workflows at exactly the same time your developers are building them.
“Internal screaming” — Me
Not much is more frustrating than developing a part of your system only to have the design change the next week into something that’s completely different or incompatible with what you just built. It’s even worse when this happens every single sprint. Whether this is just a UI change or a complete overhaul of a workflow, it’s always very impactful to your project timeline and can be very demoralising for your team. A big problem with being too gung-ho with getting your project started is not spending enough time on or skipping the design phase entirely. Sure it gets your project started quickly, but eventually that design is going to have to happen and you’re going to have to change what you’ve already built to satisfy it.
This doesn’t mean that designs will never change eventually and a big fact of project development is that sometimes this will need to happen from time to time, just don’t make a habit of it.
Make sure you plan to fix all of these things and never get around to doing it.
Congratulations, you’ve created one of the most cumbersome, unmaintainable and over-scoped projects you could. Revel in the fact that you’ll miss all of your estimates and deadlines. Relish the time you’ll spend trying to add tests while discovering that none of your code is testable. Enjoy the disappointment the stakeholders will have when you let them know for the fourth time that the launch is ‘just one more sprint away’.