10 ms·
IMO the biggest drawback to a monolith, maybe beyond those listed, is losing the 1-1 mapping of changes to CI to releases. If you know "this thing is broken", t
by 015a 4y ago
IMO the biggest drawback to a monolith, maybe beyond those listed, is losing the 1-1 mapping of changes to CI to releases. If you know "this thing is broken", the commit log is a fantastic resource to figure out what changed which may have broke it. You submit a PR, and CI has to run; getting most CI platforms cleanly configured to, say, "only run the user-service tests if only the user-service changes" isn't straightforward. I understand there are some tools (Bazel?) which can do it, but the vast, vast majority of systems won't near-out-of-the-box, and will require messy pipelines and shell scripts to get rolling.
There's also challenges with local dev tooling. Many VSCode language extensions, for example, won't operate at their best if the "language signaler" file (e.g. package.json for JS) isn't in the root of the repo; from just refusing to function, to intellisensing code in another project into this one, all manner of oddities.
Meanwhile; I don't think the purported advantages are all that interesting. Being able to write the blog post and make the change in the same PR? I've never been in a role where we'd actually want that; we want the change running silently in prod, publish the blog post, flip a feature flag or A/B test proportions. Even an argument like "you can add schemas and the API changes in one go"; you can do that without a "monorepo", just co-locate your schemas with the API itself, this isn't controversial or weird, this is how everywhere I've worked operates.
None of that is to even approach the scale challenges that come with monorepos at even reasonable sizes; which you can hit pretty quickly if you're, say, vendoring dependencies. Trying to debug something while the Github or Bitbucket UI is falling over because the repo is too large, or the file list is too long, isn't fun.
I'm not going to assert this is a hill I would die on, but I'm pretty staunchly in the "one service, for one deployment artifact, for one CI pipeline, for one repo" camp.
- dogleash 4y agoYour examples of CI and VSCode are on point, and in the bigger picture it's always about tooling. The mono/multi repo argument is fundamentally boring to me because it always boils down to whether the shape of the tooling problem is easier to work with on this or that side of the divide. The answer is always whichever tradeoffs work best for your situation, and the reason at the end of your post is as good of a reason as as any.
- xmodem 4y agoAs a big fan of the monorepo approach personally, I would say the biggest benefit is being able to always know exactly what state the system was in when a problem occurred. I've worked in large polyrepo environments. By the time you get big enough that you have internal libraries that depend on other internal libraries, debugging becomes too much like solving a murder mystery. In particular, on more than one occasion I had a stacktrace that was impossible with the code that should have been running. A properly-configured monorepo basically makes that problem disappear. This is more of a problem the bigger you are, however.
- bluGill 4y agoYou had the wrong tools. It doesn't matter if you have a monorepo or not, you will need tools to manage your project. I'm on a mutirepo project and we can't have that problem because we have careful versioning of what goes together. Sure many combinations are legal/possible, but we control/log exactly what is in use.
- xmodem 4y ago> Sure many combinations are legal/possible, but we control/log exactly what is in use. I'll acknowledge our tooling could have been better, but isn't it better to just be able to check out one revision of one repo and have confidence that you're looking at the code that was running?
- threeseed 4y agoIt depends on your architecture. If I have a services based architecture then I can jump straight to the repo for that particular service and have confidence that it is the code that is running.
- Spivak 4y agoSo instead of adopting a system that makes the problem we’re discussing not possible you use a human-backed matrix of known compatible versions? Like you do you but I’ve never seen “just apply discipline” or “just be careful” ever work. You either make something impossible, with tooling or otherwise, or it will happen.
- sn0wf1re 4y ago> If you know "this thing is broken", the commit log is a fantastic resource to figure out what changed which may have broke it git supports both diff and log for specific directories, although this may not help you if the issue was with a dependency in another folder that was updated.
- deathanatos 4y agoBut the point is that the tooling doesn't help you with it. Those are building blocks that you might build a "does this need to get build/deployed?" (& if not, what is the result of the build) mechanism with, but they are not that mechanism.
- tshaddox 4y ago> If you know "this thing is broken", the commit log is a fantastic resource to figure out what changed which may have broke it. You submit a PR, and CI has to run; getting most CI platforms cleanly configured to, say, "only run the user-service tests if only the user-service changes" isn't straightforward. I understand there are some tools (Bazel?) which can do it, but the vast, vast majority of systems won't near-out-of-the-box, and will require messy pipelines and shell scripts to get rolling. I'm not very familiar with the recently trending monorepo tools, but don't they generally provide a way to declare the dependencies between subrepos and prevent each subrepo from importing or otherwise depending on anything outside of those declared dependencies? If that's the case, then wouldn't CI be able to use that same dependency graph to determine when it needs to rebuild/redeploy each particular subrepo?
- deathanatos 4y agoWell, there aren't "subrepos" … it's all one giant monorepo. And … no? No, CI tools don't. There's generally not a tool that has the dependency graph, and it's typically not recorded. (Excepting bazel, which set out to solve this problem; lo and behold it was designed by a company with a monorepo, too.) Some CI systems I've seen have half-assed attempts at it, such as "only run this CI step if the files given by this glob changed". But a.) it requires listing a transitive list of all globs that would apply to the current step, so it's not a good way to manage things and b.) every time I've seen this mis-feature, "change" is described as "in this commit"; that's incorrect. (I have base commit B, I push changes '1 and '2, for commit graph B - '1 - '2 ; CI detects for a step that the globbed files didn't change in '2, and ignores '1. The branch is green. I merge. The result merge commit 'M changes the union of files, so now the tests run, and the commit — now on HEAD — breaks the build. A subsequent unrelated commit M - '3 doesn't modify the relevant code; CI skips the tests and delivers a green result on a broken codebase. People erroneously think "problem fixed". I have seen this all play out in person, multiple times.)¹ (A "much easier" approach is to simply cache a single build step: you hash your inputs, and compute a cache keys; see if your output is cached. If yes use cache, if no build. Computing the cache key is the tricky part and risks that famous "top n problems in computer science … cache invalidation" quote.) ¹while I know how to compute better git diffs, the difference between the common ancestor, the result once the commit gets merged, etc. are subtle. Most devs are shockingly inexperience with git and don't even get this far into the problem, and CI system's insistence on only running on, e.g., "pushes" doesn't help.
- pancakemouse 4y agoRegarding your first point, a good alternative to Bazel is https://please.build/ https://please.build/ - its build graph can solve exactly this problem in CI.
- eklitzke 4y agoI agree that the blog point didn't make a very strong argument, but there are some inaccuracies in your comment as well. With regard to the "only run X tests if X changed" problem, Bazel, Buck, and all the other monorepo build tools do this. I mean sure, if you're using some build system not meant for a monorepo you're going to have a terrible time, but who is really going to spend weeks or months converting to a monorepo and not also switch to Bazel (or something akin to it?) at the same time. In fact, I would say switching to Bazel (or Buck, etc.) for builds is a prerequisite to even starting on the path to switch to a monorepo. This is just a really useful feature even if you're not in a monorepo. Sometimes you're changing some core header file or whatever and you really do need to run nearly all the tests in your test suite. Sometimes you're just changing some fairly self-contained file and only a few tests need to run. Sometimes you change some docs in the repo and you don't need to run any tests at all. Bazel will just do this automatically for local builds (it knows what tests have transitive dependencies that have changed since the last time those tests were run), and setting it up in CI is a few lines of bash or Python. To set this up in CI you basically just check which files have changed since the last time CI ran (e.g. using git diff), then you use bazel query to find all test targets that have transitive dependencies on those files, then you feed that list of test targets to bazel test. You can set this up per-developer branch for instance, so that if you have a bunch of developers all running tests on the same set of CI machines you get good caching. With regard to colocating schemas in APIs, yes you can do that but it's really annoying to do with Protobuf/Thrift. First of all protobuf and thrift require that IDLs exist locally so they can do code generation, so if you have protobuf files split into multiple services you need a way to distribute them all which is super annoying. Additionally, in some cases there isn't a clear single owner of a particular IDL struct, for example let's say you have some date or time struct that many protobuf messages want to use in their fields. Which service do you define it in? Ignoring that, it is REALLY USEFUL to be able to modify the code for the producer of a message and the consumer of a message all at once, without having to make multiple commits in multiple repositories. This is especially true when it comes to testing. I have thing A producing a new field X, I want B to use the new field X, and I want to test that B uses it correctly. When everything is in one repo this just works, with multiple repos I need to first add the code to thing A, do a release of A (even if that's just making and pushing a git commit), then update B to consume the new thing and add the test, then if I realize I messed something up I need to go update repo A again to test it, and so on. Obviously this works and tons of people do it, but it sucks. I had to do this at my last job (which wasn't using a monorepo), and it worked but it was cumbersome and I hated it. With regard to scaling, a lot has changed in git in the last two years to make it possible to run huge git monorepos without any weird hacks. The most notable such feature is sparse indexes, which let you clone a subset of a git repo locally and have it work normally. Here's a GitHub blog post about sparse indexes: https://github.blog/2021-11-10-make-your-monorepo-feel-small-with-gits-sparse-index/ https://github.blog/2021-11-10-make-your-monorepo-feel-small... . They also have a monorepo tag which you can use to look at other blog posts about monorepos (and as you'll note, most of these are pretty recent): https://github.blog/tag/monorepo/ https://github.blog/tag/monorepo/ The biggest downside of a monorepo in my opinion is that there are a lot of things that Bazel makes way harder than the default package manager for language X. Practically speaking it's probably going to be hard to use Bazel if you don't have a dedicated build team with experts who can spend all the time to figure out how to make Bazel work well in your organization. This is pretty different from just using pip or npm or yarn or whatever, where you can get things just working in a couple of minutes and the work to maintain the build system is probably just collectively a few hours of work a week from people spread throughout the organization who can be on any team. For a small organization I can't see it being worth the effort unless you already have a lot of engineers who have a background in Bazel, for example. But there's definitely a point where the high entry-level cost to Bazel and a monorepo makes sense.
- canadianfella 4y ago[dead]
- tobyjsullivan 4y agoI've had the exact opposite experience. We have a polyrepo setup with four repos in the main stack (and a comical number of repos across the entire product, but that's a different story). My top pain point - almost painful enough to force a full consolidation - is trying to find the source of a regression. When semi-regularly discover regressions on production and want to know when it was introduced. Any other project I've worked on, that can be done with a simple git bisect. I can tell you that trying to bisect across four repos is not fun. If everything were in a monorepo, I would be able to run the full stack at any point in time. Now, if all your APIs are stable, this won't be as bad. But if you're actively developing your project and your APIs are private, I can only assume this pain will be ever present.
- threeseed 4y agoBut that's just that one scenario you've described. It's also common that if you have a dozen repos that maybe only one has changed and so when there is a defect it's trivial to determine what caused the regression. I don't think mono or poly repos are better when it comes to triaging faults. They each have strengths and weaknesses.
- 015a 4y agoThat's fair criticism. I think my counterpoint is: Generally, if I'm playing the part of the owner of some system N layers deep in the rats nest of corporate systems; I don't even want to think specifically about what broke. I know the dependencies of my system; if I have a dependency on the Users Service, and it looks like something related to the Users Service broke, my first action is probably to go into their slack channel and say "hey, we're seeing some weird behavior from the Users system; did y'all change something?" At the end of the day; they're going to know best. Maybe code changed. Maybe someone kubectl edit'ed something manually. Not everything is represented in code.
- The_Colonel 4y agoThe problem is that in microservice environments, a lot of complexity and source of bugs are (hidden) in the complex interactions between different components. I also believe that this mentality of siloing/compartmentalization and habit of throwing things over the fence leads to ineffective organization. After close to a decade of working in various microservice based organizations, I came to a big-ish monolith project (~100 devs). Analyzing bugs is now fun, being able to just step through the code for the whole business transaction serially in a debugger is an underrated boost. I still need to consult the code owners of a given module sometimes, but the amount of information I'm able to extract on my own is much higher than in microservice deployments. As a result, I'm much more autonomous too. > Maybe code changed. Maybe someone kubectl edit'ed something manually. Not everything is represented in code. That's honestly one of the big problems in microservices as well.
- osigurdson 4y ago>> IMO the biggest drawback to a monolith Mono-repo and monolith are orthogonal concepts. >> I'm pretty staunchly in the "one service, for one deployment artifact, for one CI pipeline, for one repo" camp This seems reasonable if nothing is shared. If there are any shared libraries then you are back to binary sharing (package managers, etc.) with this approach.
- 015a 4y agoThat's my mistake; I meant to say Monorepo there.
- timthelion 4y agoIt's easy with github actions to only run a workflow if a certain directory has changed: on: push: paths: - kcf/tools/kcf_graph/\* - .github/workflows/test-kcf-graph.yml workflow_dispatch: {} https://github.com/gradesta/gradesta/blob/default/.github/workflows/test-kcf-graph.yml#L3 https://github.com/gradesta/gradesta/blob/default/.github/wo...
- SOLAR_FIELDS 4y agoThis looks trivial now, but when you multiply the number of directories by 8 or so it becomes a very nasty mess very quickly. I think that the idea of only running what changed makes a lot of sense, I just think that managing that in declarative yml falls apart VERY quickly once you hit an inkling of scale.
- hyperhopper 4y agoAnd what about when one directory relies on another directory? (The answer is comprehensive tooling, not primitive hacks)
- sanderjd 4y ago> Meanwhile; I don't think the purported advantages are all that interesting It's one of these things that's really obvious if you've worked in a well configured monorepo, and very hard to convince people of who haven't.
- nonethewiser 4y agoI dont understand. You can have 1 repo with multiple services that can be deployed independently. Edit: perhaps the difference is that you said "monolith." I guess I'm not sure precisely what you mean by this, but context makes it seem like you're using it synonymously with monorepo. Since that's what this thread is about.
- 015a 4y agoApologies; I mean to say Monorepo; and specifically the idea of having one repo with multiple services deployed independently.
- fbdab103 4y ago>Many VSCode language extensions, for example, won't operate at their best if the "language signaler" file (e.g. package.json for JS) isn't in the root of the repo; from just refusing to function With VSCode, a workaround is to use workspaces. Define a workspace and add each subproject folder as its own entity. VSCode will treat each folder as a project root where the language specific tooling will work as expected.
- marcyb5st 4y agoI just want to comment that you are correct. Bazel allows that and so should any tool that can build dependencies DAGs. Once you have that it's absolutely feasible. The major issue is that you need to be diligent at bookkeeping your dependencies. Bazel enforces that in the BUILD files and since everything is run in a sandbox you can't easily take shortcuts or you'll get missing dependencies errors.
- BillinghamJ 4y agoThere's a very simple solution to that, if your systems & processes are reasonably lightweight. Just build and deploy everything on every merge. Compute is fairly cheap, and if running in parallel, it doesn't have to take long. You can also take it a step further and have "mono" binaries/container images, where you specify the service to execute as the first argument. I've been doing this for about 5 years now, having a single output artifact for each language being used. It works great. If you're careful about your optimisations, you can go from hitting the merge button to having 100+ services deployed on production in about 60 seconds Arguably it's a bit of an extremist approach, but if you have a situation where technically you're deploying thousands of times a day, you get pretty good at making the process reliable and robust