Skip to content

Instantly share code, notes, and snippets.

@Riebart
Last active May 7, 2024 18:16
Show Gist options
  • Save Riebart/02a69371c100385f453c9d8eb01dd585 to your computer and use it in GitHub Desktop.
Save Riebart/02a69371c100385f453c9d8eb01dd585 to your computer and use it in GitHub Desktop.
Thoughts on git branch workflows and microservice code organization

Comparing branching and workflow strategies

Summary of existing

There's several existing strategies for deploying and managing development work and release. They each have their own opinions, but roughly cover a few major situations:

  • Developing a new feature
  • Which branches should be, at all times, deployable, tested code
  • Under what conditions a code review or approval is required to merge code into a new branch
  • How many deployment environments (e.g. production only, or staging and production) you intend to have available, and to whom
  • Deploying to UAT, staging, and production
  • Deploying a hotfix to the released version
  • How forks of a repository contribute back to upstream

In all cases, feature branches can be replaced by feature forks, which has the advantage of permitting contributors without granting write access to the primary repository.

git-flow

A common flow that supports a wide range of use cases, and is well detailed in Atlassian's documentation on the topic. git-flow is distinguished by a few key principles:

  • Designed around the concept of "a release" as a specific thing
  • Production code is kept in the master branch
  • master receives new commits only in the form of squashed merge commits from the Release branch, and each new commit should be taqged with a version number
  • Release (or staging) code is kept in the release branch, and should include production-ready code that is not slated to be deployed to production yet
  • The mainline integration-ready code is kept in the Develop (develop) branch.
  • Features are worked on in feature branches that branch off of develop
  • Hotfixes are worked on in hotfix branches that branch off of master, and are then merged into master, and optionally release and develop as appropriate.
  • Code review and approvals occur for any merge into develop, release, or master.
  • New feature lifecycle requires development to go through: feature, develop, release, master

Issues with git-flow:

  • The overall structure is intended for development cycles that need to maintain multiple testing environments and approval gates, and is likely overly cumbersome for smaller projects with less sensitive production deployment considerations.

Tooling available:

GitHub Flow

A simplification of git-flow, GitHub Flow is described in GitHub's page on it and removes several of the branches that exist in git-flow and is instead aimed at smaller, more agile development workflows where integration testing occurs continuously with commits, and so does not require as many approval and review gates when merging a feature into master. GitHub Flow is distinguished by a few key principles:

  • Designed around the concept that there is no such thing as "a release", only the released version of the code
  • Anything in master is deployable.
  • Any push to master should be immediately deployed.
  • Features are worked on in feature branches that branch off of master
  • Pull Requests from feature branches into master form the basic structure for review and approvals, as well as triggers for integration testing.

Issues with GitHub Flow:

  • Adopting GitHub Flow for complex systems will likely require that integration testing be automated and continuous in order to achieve confidence that code going into master is ready to be deployed.
  • It is challenging to include a staging environment into the workflow, as it disrupts the concepts of the constraint that code in master be "immediately deployable". Specifically, the definition of what it means for code to be "deployable" needs to be re-worked.
    • One way to define this is "ready for integration testing", which makes master akin to develop in git-flow, leaving a vacuum for production code, and the triggers to push there.

Specific microservice challenges

Microservice development, especially development of services spanning multiple repositories, presents a specific challenge that integration testing environments can be challenging to setup. Well-defined integration tests must span multiple repositories, and will typically depend on API and interconnect contracts that are documented (or, preferably, codified) in the various repositories.

These API and interconnect contracts (e.g. an API definition using Swagger) can be maintained and used to validate that upstream and downstream microservices are exposing the expected functionality. It becomes challenging, however, to maintain a consistent, unified definition of what those contracts are if the contracts span multiple services (e.g. if A provides and API that is used by B and C, then all 3 repositories must, to some extent, contain that API definition which must be kept in sync across all repositories. Alternatively, there can be a separate repository that holds the API definitions and is used as a git submodule in all dependent repositories).

Source code structures for microservices

There is an approach to structuring a microservice-based application as a monorepo, which alleviates many of the integration challenges, as it enforces that all microservices are referencing the same shared assets at the expense of source code access control (which can be partially mitigated through forking and pull requests).

Monorepos have a significant disadvantage if multiple apps share the same microservice component, as this requires that care be taken to ensure that component's code be synchronized across repositories for each app that uses it, that the component be broken out into its own repository and included as a submodule, or that all apps that use it be in the same repository. Further complications arise if different apps require that the component be forked to behave differently, or to support other features that are other apps are disinterested in.

The alternative is microrepos, where each microservice is maintained in its own repository, potentially pulling in other repositories continuously as submodules. This approach grants microservices flexibility to define and deploy their own infrastructure for testing, integration, and even production, with increased source code access control, but requiring at least one shared repository that holds clustered infrastructure definition and tooling, and the authoritative definitions of interconnect contracts between microservices.

Combined git flow and code organization

The correct choice depends on a few factors:

  • Do you want "a release"?
    • Yes: git-flow
    • No: GitHub Flow
    • Don't care: {keep reading}
  • Do you want a more than one "release" ready branch?
    • Yes: {keep reading}
    • No: GitHub Flow
    • Don't care: GitHub Flow (because it is simpler and results in a stronger opinion towards continuous integration and deployment)
  • Are you able to invest the effort to build per-commit integration tests (across repos, if using microrepos)?
    • Yes: GitHub Flow (as continuous integration provides reasonable guarantees that multiple features are working together without human review and testing)
    • No: git-flow (as with it's multiple branches and release stages, there are opportunities to deploy to shared development, staging, and production environments to manually verify integration testing)

A final note on microrepos

Ensuring that the expectations between repositories/microservices stay consistent is a crucial step to working towards automated and continuous integration testing. This will likely necessitate that microservice repositories be held together by an app-level repository (or repositories) that store this shared contractual information, as well as shared infrastructure definitions and secrets.

References and further reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment