This is about how many trunks a team has, and how many applications they make from it/them. It’s pertinent to Trunk-Based Development (TBD), of course, which some high-throughput companies with many thousands of developers like Facebook do. They do it because developers merging to working-copy multiple times a day, and committing to a common place is the best way to facilitate speediest Continuous Integration (CI). Also, this article is not about how developers use DVCS on their own workstations, but about the shared/managed source-control repository common in enterprises.

Before we start, some definitions:

  1. Application - something you build and deploy, comprised of modules. One or maybe more processes.
  2. Web App - an application that uses a browser to interface with end-users.
  3. Web Service - an application that uses HTTP to interface to remote non-human things (this includes RESTful services).
  4. Module - something that is built and deployed with an application, that the application depends on (linkable objects including JARs, DLLs) which typically may have their own build file.
  5. Third-Party Modules - Modules not build by the organization in question, like Log4J.
  6. Process - as you’d expect: an OS launched thing built in a homogeneous technology (but ignoring whether horizontal scaling happens for the companies load requirements).
  7. Continuous Integration - how and when commits are automatically built, linked, deployed temporarily and destruction-tested.

A startup with one Web App

Let’s say this startup has a single Web App written in PHP and using an DB (MySql or Postgres) in 2004. It is a social network, and we’ll call in Bookface. All the technologies were great choices for this thing. There’s some horizontal scaling, but that’s of identical processes. Ignoring Apache, any load-balancer (and infrastructural pieces), the dissimilar process count is 1:

According to our invented history, Bookface had a single trunk (Subversion). Lets plot that number of trunks to the number of applications on a chart:

The hypothetical site (www.bookface.com) may or may not comprise a number of modules, but all of those modules can be built in series in a single invocation. Bookface do not need CI yet, but would be much better with it. Mere ‘Rigor’ around commits in a Trunk-Based Development (TBD) setup, will only get you so far for so long.

The single trunk, and single app, we’ll refer to as ‘A’, and accept that is a how Bookface started.

Making more than one application?

Specifically, what do Bookface do when they start their next application. Say that is an iOS or Android thing that provides a second way to see/manipulate data. Perhaps they’d deploy a Web Service built in to the main Web App. Alternatively they might deploy the same web service as a standalone process with it’s HTTP interface, with the ‘old’ Web Application depending on it. If it’s a standalone, they might deploy it at the same time. If they don’t deploy always at the same time, then the web-service own release cycle and forwards/backwards compatibility, and must be measured against Conway’s Law to ensure that it’s not gratuitous. Here’s that stack with an additional Web Service tier now:

In terms of their production stack, the dissimilar process count is now 2. That’s the Web Application and the Web Service (as before we ignore infrastructural pieces, they did not compile themselves). In terms of applications, the count is now 4: 2 + 1 (Android client) + 1 (iOS client).

Bookface now has choices as to the organization of source in order to make making more than one application. I’ll outline them these alternates (‘B’ through ‘D’ below). They are certainly not a progression:

B: Having all source in one trunk

Bookface have the source for all four applications in one trunk. They kick off a build script from root, that knows which application it is building. With that knowledge, the build script subsets the trunk into only the source/modules that need to be built for that application. In Java-land a maven profile that created then build a directed graph of sub-modules is popular. That’s not the only way though for Java.

Each application release according to a different schedule, which means release branches may contain source code that is not going live at all. Nobody really worries about this, as branching is not costly in the modern source-control tools, and there’s trust in (and verification of) the build scripts.

Note though, this absolutely cannot be done without some really hard-core CI pipelines (read Jez’s book - Continuous Delivery)

Benefits

  1. Common code ownership.
  2. Continuous Integration that reports defects with seconds of them being committed to the trunk.
  3. Ability to deploy separate applications separately.

Challenges

  1. The “Diamond Dependency” Problem requires clever thinking to solve, if all dependencies (particularly Third Party Modules) have to be in lock step (they do).
  2. Having a supported workflow for developers changing code that’s not in “their” module (thin vertical slices).
  3. Making sure changes to shared modules are fully tested against tens or hundreds of applications that use them before committing.
  4. Making sure changes to shared modules are in line with the directions of the 10s/100s of apps that use them.
  5. Worrying about the size of representation of the ‘checkout’ on the developer workstation, maybe needing tooling to reduce that.
  6. Worrying about the update/sync/pull/fetch duration being a disincentive to doing it 20 times a day, needing tooling to reduce that.

C: Separation into a small number of repos/trunks, many more applications.

This is like ‘B’ above, but there’s a modest split of source code between two repositories. Say ‘front-ends’ and ‘middle-tiers’ for Bookface. For ‘middle-tiers’, all Web Services that pertain to the Web Application, the iOS client, and the Android client go out in one deployment. It does not matter which one is needed for a release, they are all part of the same process in production. That’s just one way of slicing source, and grouping deployments. Another could be that you make separate processes for each of the Web Services, (even on a release branch), but choose which ones go out in each deployment.

It seems to me that there is not enough difference to ‘B’ to make any benefits clear, and that this only introduces management and CI-ordering overhead.

D: Many trunks, many applications

Bookface decided to make a trunk/repo for each modules. Perhaps these were those modules in separate repos/trunks: bookface-persistence, bookface-logging, bookface-admin, bookface-operations, bookface-web, bookface-cdn, bookface-orm, bookface-rules, bookface-services and bookface-temlating.

It is likely that they also made some team separations for all or many of these, and reflected that in the organization. Companies doing this might be doing feature branches rather than trunk based development.

Benefits

  1. Suits a very hierarchical organization of development work, including permissions for separate repos/trunks.
  2. Discrete versioning of individual pieces.
  3. Diamond-dependency problem reduced.
  4. Developers only checkout what they are working on.

Challenges

  1. Not seeing a reduced developer throughput.
  2. Maintaining versions for dependencies in build files in many repositories/trunks.
  3. Making sure developers update/sync/pull/fetch all the repos that they are working on frequently enough.
  4. Having tooling for developers to acquire appropriate binary versions of dependencies from repos/trunks they are not working on (they don’t have a checkout).
  5. Using CI effectively to integrate ‘latest’ of all. In reality, this pushes your integration testing towards release dates, meaning you’re risking cost-of-change issues, which is the antithesis of CI.
  6. Having channels for developer asking for changing to code that’s not in “their” module.
  7. Making sure changes to shared modules trigger automatic rebuilds/tests of modules that use them.
  8. Preventing Conway’s Law from seriously impacting nimbleness.

Last thoughts

Summary

Here is an attempt at a summary:

Aspect A B C D
Continuous Integration Fast Fast with tooling Fairly Fast Slow or Late
Common Code Ownership Yes Yes Some Not Really
Thin Vertical Slice Easy Easy OK Hard
Versions of Modules Don’t Think About It Don’t Think About It Some Manual Accounting of Versions Much Managing of Version Dependencies
Releasing to Prod Turnkey Turnkey per App Think About Dependencies Worry About/Manage Dependencies


I prefer ‘B’ if it wasn’t clear: All source co-mingled in one trunk, taking advantage of lightweight branching of modern source-control tools. I’ve seen it work at huge scale.

MicroService Architectures

Companies implementing MicroService Architectures could be doing any of these. MicroService Architectures is more about fidelity of deployment than organization of source. I think such companies can be perfectly successful with a reduced amount of trunks (‘B’ or ‘C’), but ‘D’ might be common.

Companies doing cookie cutter scaling are likely to be doing ‘B’ or ‘C’, and very unlikely to be doing ‘D’.

Facebook?

Bookface is a reverse engineered beginning to Facebook. I’ve no idea about the development realities of their early days.

We know that Facebook use one trunk for their main PHP website, and used to have a special compiler to make that into a 1Gb exe, but now is a VM solution. Where the source code is for lower tiers of their stack (same or different trunk/trunks), and what paradigms they have for releasing ‘services’, and whether that’s at different moments to the Web App, has not been talked about yet.

Google?

We know they use Perforce, and have hundreds of applications. They have not talked about their branching model, though they have talked about many aspects of their build infrastructure on their engineering blogs. If anyone has links that publicize any more information about Google, please email me :)

Thanks

Thanks to Mark Needham for some comments before publication.



Comments formerly in Disqus, but exported and mounted statically ...


Wed, 10 Apr 2013Randall DeFauw

Hi Paul,

Since you recommend option B, I'm curious to hear your recommendations on how to tackle the associated challenges...

1. The “Diamond Dependency” Problem - Were you thinking that you'd have more than one pipeline? If you have a single pipeline you'd find out pretty quickly if someone introduced a dependency problem, right?

2. Having a supported workflow for developers changing code that’s not in “their” module - Assuming you haven't set up access control to restrict what people work on, would you rely on the first testing phase of the pipeline to catch any inadvertent commits to other modules?

3. Making sure changes to shared modules are fully tested against tens or hundreds of applications that use them before committing. - Sounds like a good place for pre-flight builds. Otherwise you might pass the commit phase testing and break a lot of downstream phases.

4. Making sure changes to shared modules are in line with the directions of the 10s/100s of apps that use them. - I guess you'd want enough acceptance testing that you can catch errant behavior even if it doesn't directly violate an existing requirement.

5. Worrying about the size of representation of the ‘checkout’ on the developer workstation, maybe needing tooling to reduce that. - I've seen a couple of good tricks in this area. One is using more selective SCM checkouts to narrow the data given to the user, and the other is relying on network storage for working copy management. The latter approach works pretty well for projects with over a few GB of data included.

6. Worrying about the update/sync/pull/fetch duration being a disincentive to doing it 20 times a day, needing tooling to reduce that. - Do you mean in the sense that it just takes a long time to figure out what's updated, or perhaps that there's a lag in transmitting the updates?

Thu, 11 Apr 2013paul_hammant

1) In terms of CI, yes you'd have pipelines for all deployable sets of binaries. Catching the problem would be quick, arbitrating between 'team coco', and 'team america' one whether "we" should be using Hibernate 4.3 or 3.2 is harder.

2) wouldn't set up access control as you hint, and yes to CI catching things.

3) yes.

4) yes, and forums to discuss directions for shared tools (committees, mail-lists etc).

5) Yup, your source-control tool Perforce does that well - client-spec (Subversion has an approximation).... I hate to say it but ClearCase can do it too.

6) relates to 5 perhaps. Only "sync" what you're about to build.

Thu, 11 Apr 2013Ákos Frohner

For Google I would recommend reading http://google-engtools.blog...
I will be also talking a bit more about branching at http://releng.polymtl.ca/ -- basically "B".

Thu, 11 Apr 2013paul_hammant

yay!

Thu, 10 Dec 2015Kevin Scott Phillips

Nice post. Ironically, you wrote this post several years ago and it is still relevant today. The company I work for follows a pattern much like your B pattern. Surprisingly, this is one of the few blog posts I've come across where such a pattern is described in detail.

Ironically, however, the second "benefit" you describe for that pattern, namely "Continuous Integration that reports defects with seconds of them being committed to the trunk.", seems to be unachievable in certain cases including ours. Perhaps you could put that benefit within a context that explain how you have been able to achieve such fast turn around times at scale.

See, we are primarily a C++ shot, and our codebase has close to a dozen different applications built upon several common "framework" layers, all managed from a single trunk. On high-end hardware (i7 CPUs (8 core, 3.4Ghz+), 16-32GB RAM, SSDs, GigE networks, parallel build operations, etc.) it still takes in excess of 8 or 9 hours to checkout, build, test and package the entire codebase from a clean for both 32bit and 64bit platforms, and debug and release configurations. Of course incremental builds are somewhat more reasonable, often being completed in under an hour, but we have not yet found any way to reduce the turnaround times to the degree you are claiming. Not only does this pose problems for CI and automation, but it presents unique difficulties for developers on their local builds as well. In the degenerate case, it takes so long for a developer to compile and test their code changes locally that by the time they complete that procedure someone else has committed more changes that they then need to pull locally before committing. But then they need to repeat the whole process again to make sure the new changes they've just pulled in haven't introduced any new problems, and so the cycle repeats.

I think the issues we are experiencing may be particular to the tooling. You refer to Java in your article, for example, and mention how Maven takes care of managing the dependencies and generating build graphs automatically. I believe .NET has similar tooling as well. We are currently a Microsoft shop and unfortunately the Visual Studio tools don't scale well in that regard (try loading a single solution file with thousands of projects in it and you'll see what I mean), and unfortunately any non-Microsoft tools that may be able to help in this regard typically don't play nice with the Visual Studio toolset.

I'd love to hear your thoughts on this and to see whether you've heard of anyone successfully applying this pattern in a production environment at scale. Unfortunately for us, it seems the only "practical" long term solution may be to consider patterns similar to your options C and D, and then looking for ways to manage the large number of interdependent modules individually. However, once again I have not yet found any off-the-shelf tools that are compatible with C++ and Visual Studio capable of doing that so we'd be faced with hand-rolling a solution for that as well. :(

Fri, 11 Dec 2015paul_hammant

Lots of Games studios use "one big trunk" (and perforce in particular). Frank Compagner of Gorilla Games talks that it in various blog entries and videos. Even if you have 1000 Jenkins slaves to do builds in parallel, its still a 8-hr turn around on good/bad news.

No. Your answer is to do what Google do. Their fabled build system "Blaze" was open sourced as Bazel. C++ is their heavy-lifting language and is even used from many Java solutions. ExGooglers at facebook made "Buck" in Blazes image (before Bazel was open sourced).

These two (three) build systems allow you to build a direct graph of modules. The considerable CI infra that is tied to them allows them to focus at the directory level and build linkable objects per-directory. That way it's easy to see what has changed versus the last build (even for huge trees) and rebuild only the bits that have changed. Similarly, impacted tests can be determined and only those are run. You might be able to ring the per-commit CI-driven build/tests cycle down to minutes. Maybe, in a different CI loop, you still build "clean" back to back, as a safety net on the possibility that the incremental build might have false positives.

If you have too much source for Git to handle, consider Perforce. Even Microsoft used it back in the day - recompiled for them as "source depot" (SD).

Fri, 11 Dec 2015Kevin Scott Phillips

The main deterrent for us in adopting tools like Bazel and Buck is that they don't interop with Visual Studio. Make, Gradle, Scons and pretty much all the other similar build tools I've evaluated have the same limitation. At best the tools allow you to generate Visual Studio projects from their own custom, proprietary configuration files. This leads to maintenance issues because developers are accustomed to making configuration changes directly in the IDE, and those changes don't automatically get reflected back to the config file the third party tool used to generate the project. I mean, short of using the Visual Studio IDE as a glorified text editor and forcing developers to maintain the build configuration using text files, I'm not sure how that would be maintainable.

Do you know of any tool or development shop that has solved this particular problem? For example, is there any way to setup Visual Studio such that any project changes that are made via the IDE are reflected somehow back to the source files that generated the project without manual intervention by the developer?

Fri, 11 Dec 2015paul_hammant

In the early days of Maven, the worst-best-IDE Intellij didn't support it in the UI. So you were forced to edit pom.xml files in the IDE as a "glorified text editor" :). The flipping to the command line to run a build. Maven has a recursive build. From root, for you that would still be 8 hours. but maven would allow you to cd all the way to a leaf module and build/test that. It achieves the same goals as Blaze, but implements differently. For Blaze (etc), you remain in the root dir for the project and pass the sub-module as a parameter when you wanted that focussed build/test. You have to have done a build test at least once of course for the linking to work seamlessly. Now Google's other genius for that was that they put previously built permutations in an LRU cache for the whole team (and CI to depend on). Say you have a directory (a module) in a tree with 30 C/C++ files in. Make a hash of the contents of all of the sources within, have that as they key, the payload being the intermediate linkable object for the same completed compile artifact. Ties you all to the same V.e.r.s.i.o.n of the compiler but that's not a bad thing. I can't remember whether Googles LRU cache backed by memcached or not (I left in Jan 2009).

To me - shortening your build times trumps developer IDE comforts. Sure - without a plugin for the IDE (Google made that too), you are dependent on the command line, and the ability to attach a remote debugger to that process (say you're breakpointing a failing test).