10 Workspace Guidelines for a Superior Developer Experience

1. Make Docker the only dependency.

The “works for me” syndrome plagues many developer teams. It’s essential that anybody be able to easily create the same environment. As such, elaborate, manual setups should be banned. We live in the era of containerization and teams should leverage it. To set up code, we should only expect to see the Docker runtime and Docker Compose on the host machine — nothing else! It should not matter if the machine is running Windows, macOS, or Linux and what libraries are present. Such assumptions are exactly what lead to broken setups. For instance, there should be no set expectations about a specific version of Python, Go, Java, etc., being present on the developer’s machine. Setup instructions must be automated, not codified in READ.ME files.

2. Remote or local should not matter.

Setup should work regardless of whether a developer runs code on their own laptop or on a cloud server via an IDE’s remote development/SFTP plug-ins. This should hold true by default and if there is a case in which this cannot be done, a cause for an exception must be justified and documented

3. Ensure a heterogeneous-ready workspace.

A good setup should accommodate multiple microservices written in multiple programming languages, using multiple data storage systems. A microservices architecture assumes the ability to combine heterogeneous microservices; it doesn’t mean just putting one codebase in one container or standardizing on one technology stack. Too often we see “[some-language] microservices framework” in marketing materials. Well, guess what — if 100% of your microservices are written in Java, there is something wrong with the setup, and no, you don’t get to chuckle if all your services are written in a “cool” language like Go.

The Rule of Twos

4. Running a single microservice and/or a subsystem of several ones should be equally easy

Let’s say an airlines reservation system is implemented as three microservices. A developer should be able to check out any particular microservice individually and work on it, or check out an entire subsystem of interacting microservices (the reservation system implementation) and work on that. Both of these tasks should be very easy.

5. Run databases locally, if possible.

For the sake of isolation, for any database system’s local, Docker-ized alternatives should be provided, and it should be trivial to switch over to cloud (e.g., AWS) services via a configuration change. As an example, MinIO can act locally as a drop-in replacement for S3. Many AWS service alternatives can be installed via this GitHub site.

6. Implement containerization guidelines.

Not all containerization approaches are equal. Anybody can haphazardly stick code into a Docker container, but making a containerized coding environment developer-friendly takes more effort. Following are some principles that we have found essential:

7. Establish rules for painless database migrations.

It is extremely important to manage databases and the data in them in a way that supports and enhances team collaboration. Changes to data schemas must be codified and applied without any manual steps. The following list of principles facilitates painless data management in a microservices environment:

a. Flyway hosts this introduction to database migrations

b. See this blog post by Daniel Miranda et al. about database migrations for Cassandra

c. Check out this example of using Node’s DB-migrate-SQL for a MySQL database

8. Determine a pragmatic automated testing practice.

Automated testing is a complex subject. We have certainly seen both extremes of the spectrum: some teams giving up entirely on automated testing, and others being overzealous on test-driven development to the extent of it becoming a problem. We advocate for a measured, pragmatic approach to automated testing, one that balances developer experience with quality metrics and accommodates the different personal preferences of various developers on the team.

9. Branching and merging.

Virtually everyone these days uses some form of code version control system. While the basics of version control–driven development are well-understood, it’s worth reminding ourselves of some core principles of good branching hygiene that all team members should observe for a happy collaboration:

10. Common targets should be codified in a makefile.

Every code repository (and generally there should be one repository per micro-service) should have a makefile that makes it easy for anybody to work with the code, regardless of the programming language stack used. This makefile should have standard targets, so that no matter what codebase, in whatever language the developer clones, they should know that by running make a run they can bring that codebase up, and by running make test they can run automated tests.

  • start: Run the code.
  • stop: Stop the code.
  • build: Build the code (typically a container image).
  • clean: Clean all caches and run from scratch.
  • add-module
  • remove-module
  • dependencies: Ensure all modules declared in dependency management are installed.
  • test: Run all tests and produce a coverage report. • tests-unit: Run only unit tests.
  • tests-at: Run only acceptance tests.
  • lint: Run a linter to ensure conformance of coding style with defined standards.
  • migrate : Run database migrations.
  • add-migration: Create a new database migration.
  • logs: Show logs (from within the container).
  • exec: Execute a custom command inside the code’s container.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store