How We Do Software Engineering At ProcedureFlow

James Hickey
ProcedureFlow Engineering
8 min readApr 1, 2022

We are a SaaS startup based out of Canada with a team of 5 fully remote software engineers. We’re growing our team this year - we’re hiring!

We know that it’s hard to get an idea of what a prospective company’s culture is like: how they work and what they value. This article highlights what the software engineering culture is like at ProcedureFlow.

Our Tech Stack

Our Tech Stack

Our tech stack is very similar to Stack Overflow’s:

  • .NET framework and C# for our backend web servers and background process workers
  • We’re using React for any non-trivial frontend code, with some older jQuery and Backbone code that we’re converting to React over time
  • PostgreSQL is our database our choice
  • We’re using Redis for all sorts of things like caching, rate-limiting, distributed locks, async messaging, etc.
  • All our web servers run on AWS with load balancing, edge security, etc.

While microservices are all the rage these days, they aren’t always the best choice. As Martin Fowler puts it:

Monoliths and microservices are not a simple binary choice. Both are fuzzy definitions that mean many systems would lie in a blurred boundary area… no sensible architect treats them as a comprehensive partitioning of the architectural space.

As far as monoliths go, GitHub was a monolith up until recently, Shopify has used a monolithic architecture for their core services and Basecamp is what one might call a “modular monolith.”

This architecture works for us because of reasons like operational simplicity (e.g. deployments and automation are easy). This helps our company and team of developers iterate and build things quickly.

Security!

Security is a big deal for us. You’ve more than likely worked at companies that have poor or no real security practices. I’ve personally worked at places that stored passwords in plain-text 😅.

However, at ProcedureFlow we’re SOC 2 certified. We have annual penetration tests and monthly vulnerability scans conducted by third parties. We have policies that we need to follow and document, which are audited on a regular basis.

We’ve built our production infrastructure with things like redundancy and high availability in mind. All our code is reviewed.

We’ve written an entire article on how we chose to do API authentication for the foundation of our new API infrastructure. This might give you some specific insights into how we work and how we value security.

For more on this topic, you can check out the security page on our website.

How We Integrate Code

For storing, managing and reviewing our code we use GitLab.

All the code that ends up in production goes through a code review. Getting a second pair of eyes on our work has the benefit of double-checking things related to performance, testing, code conventions, etc. This not only includes application code but also important documentation like architectural decisions records.

We try to keep our pull/merge requests large enough to be meaningful yet small enough to be able to review well and in a timely manner.

Here’s a general example of how we use Git:

  • We might spike a larger feature on a certain branch X with commits per logical piece of the feature.
  • If we need to go back and update part 2 of 3, for example, we might interactively rebase part 2 using Git.
  • Once we’re done a spike, we can start creating individual branches from each of those commits and create separate MRs for each smaller piece of work.
  • Our merge requests are reviewed and merged into `master`. We also have a `production` branch to keep track of what is deployed.
  • We use feature flags quite often too. This helps to keep MRs small. We can push non-active code to master. Later, we can enable new features for specific customers using the same flags.
How Some Of Our Team Manages Branches In Progress

How We Test Code

Our code has been built from the beginning with testing in mind. As best as possible, all code that we write will have appropriate unit tests included.

We use a combination of unit and integration tests for our backend code. We also have a frontend test suite for our JavaScript code.

Our codebase has convenient tools so that our tests can easily hook into a real database to make integration-like testing straightforward. We can easily build integration tests by making our test class inherit from a special base class which exposes a database session to work against. Kind of like this:

For these kinds of tests, the database is cleared after each test. This means that each individual test will have its own context that can be built up and changed as needed. We don’t have to worry about pre-existing users, objects, sessions, etc. in our database tests!

We also have custom command-line tools that help us to run all of our tests with one command: backend tests, frontend tests, linting, package security monitoring, code compilation checks, etc. These tools help with deployments too.

How We Deploy Code

Deployments are automated through our own custom command line tooling. With one command we can trigger a full pipeline of testing, building and deployments. We have all the benefits of continuous code integration and quick-and-easy deployments.

We use other tools like Terraform to deploy our infrastructure to AWS. By using infrastructure-as-code we get many benefits like immutable deployments, documented infrastructure and the ease of being able to configure new infrastructure.

We can, for example, create some new API infrastructure in our staging environment via Terraform. Once everything is working and tested, we can deploy the same consistent configuration in production without having to manually turn on servers, configure load-balancers by hand, etc. each time we deploy our new infrastructure.

Feature flags are a tool we love to use. This enables us to push smaller chunks of code while keeping features that are not available hidden from sight until finally released. We have the option to release specific features to specific customers too. This helps us to be able to beta test new features and products 😊.

How We Plan And Work Together

Daily Planning

We don’t do daily stand-ups. Let’s get that out of the way 😅.

Well, we kinda do. On Slack each morning, we literally just list out bullet points of what we expect to be working on that day. This just helps to get an idea of what everyone is working on. We all know that the typical “stand-up” usually provides zero benefits at the cost of 20 minutes of wasted time. It’s great that our team has the flexibility to do what works for us.

Cycles, Planning And Retrospectives

Every 3 weeks we do a retrospective of how things went for each engineer. We have a chance to ask each other questions and demonstrate some of the cool stuff we’ve been working on. Since we aren’t all working on the same features most of the time, it’s nice to have dedicated time to see how other parts of the product and codebase are changing.

At the beginning of each 3-week cycle, we do a little bit of planning. Nothing crazy, but generally we take a broad look at what larger features and tasks are in scope for the next 3 weeks. We discuss what we think we’ll be able to accomplish in the 3 weeks and make sure we are ready to coordinate on any tasks that need coordination.

Planning And Performing Maintenance

When we have major maintenance that needs to be done we work closely together. We make sure to plan and document as much as we can up-front. This “game plan” will give us the steps that we’ll need to follow when performing the real maintenance.

A full run-through of this game plan will first happen against a test environment that closely matches production (remember how Terraform gives us the ability to re-create our shared infrastructure really easily?).

When we perform maintenance together, we like to use the pointing and calling method to make sure things go well. This method ensures that each action performed during maintenance — like buttons that are clicked, commands that are executed, etc. — are double-checked and verified by at least one other person in real-time. This just helps to reduce the chances of human error and mistakes.

Documenting Our Work

We are SOC 2 certified, which means we document almost everything we work on. This helps satisfy our auditors but also keeps us accountable. It really helps when we don’t remember if we completed a certain task —we just go check the documentation!

For example, we document seemingly simple things like giving email or application access to a new employee. While it seems mundane and pointless, it records:

  • The fact that certain security policies we enacted in a timely manner
  • Who performed the work
  • Other tasks, issues or merge requests that are relevant
  • Other documentation, when needed, can be associated with any body of work we do

As mentioned already, we use Architecture Decision Records to document important architectural decisions. These are stored as markdown files in version control (e.g. Git). These help us to refine and ensure our pre-code decisions make sense. Documenting these important decisions also helps give us context when looking back at “why did we choose to do X that way?”

How We Decide What Products And Features To Work On

Our company works closely with our customers. We think deeply about any patterns that appear from the needs of customers. For example, we try our best to detect if multiple customer needs are pointing in the same direction and would warrant the same “feature” to solve those problems.

Because we work in small iterations, we get early feedback from our customers on what works and what doesn’t. All of this is made possible because of our philosophy of product planning and how we are able to work in small chunks. This is made possible by everything you just read above — a simple tech stack (not simplistic though), collaborative teamwork, feature flags, small chunks of work being reviewed, code convention consistency, etc.

In the end, this is the goal of all the technical “stuff’ that we do — improve the lives and work of our customers!

We are driven by building things that our customers really need and would truly benefit from. There’s nothing more satisfying for our software engineers than to know that the thing they just shipped to our customers is making our customers excited about using our product!

The End

We hope this article gave you an accurate and informative look at how we do things at ProcedureFlow. By being flexible and agile in how we work, we are able to ship value to our customers quickly and iterate on our features using their immediate feedback.

If this sounds like the kind of environment you’d want to be a part of and work that you love to do — we’re hiring!

--

--