What is the best way to setup a multi package project?

I am working on a project with dependency graph like this:

  • App
    • Utils
    • Codec
    • VM
      • Codec
      • Utils
    • Logics
      • Utils
      • Codec
      • VM

Right now all the packages are in a mono repo and use relative path to locate the dependencies and that works fine.

The APIs are not stable so having everything in a single repo makes it easy to do cross package edit / breaking changes to API.

However, I have a requirement to move VM to its own repo.

What options do I have?

One way is to move every package to its own repo. However I would like to avoid that because of overheads. Some downsides:

  • Overheads to manage many repos
  • A lot of unnecessary version bumps are needed compare to mono repo approach
  • Very poor DX when doing major refactoring. This project is still very early stage so refactoring are expected.
  • Extra attention are needed to ensure deps are aligned. e.g. all packages should use a same version of swift-log.

Another way is to use git submodules. However I am not sure how do get it to work. I can move VM to its own repo and add it as submodule to the App repo. But VM depends on Utils and I can't really add Utils as submodule to VM because other packages also depends on Utils. So I have to move Utils to its own repo as well and reference it by git url. Then we are back to the previous solution.

Any other ways? Any good big open source project that I can check for inspiration on how they setup the repos?

1 Like

I have a mono repo with many packages, which works really well for me. It's very easy to move stuff around between the packages and the packages and the main app, also because I'm coming from a situation of quite extensive tech debt and I cannot fix everything in one go. I would not go for an external package unless there's sufficient separation and stability on the API that connects the modules.

  • A lot of unnecessary version bumps are needed compare to mono repo approach

You should pin your packages to commits, not to versions. I had the same issue when I used that approach on a demo application where I separated out the Model layer: GitHub - LucasVanDongen/MuncheeModel: The Model section of the demo app called Munchee

It drove me nuts and I would never do it again. This was just one external package. If you stick to commits, then you can just push, copy commit has, update your project and you're done. It's considered even a good practice without considering ease of use: Pinning Swift Package Versions: Predictable SPM Package Versions Across All Machines

My mental model is now that versions are for external consumers, not for internal development. So you should not only have a limited amount of releases, but they also should have release notes and clearly indicated goals.

  • Extra attention are needed to ensure deps are aligned. e.g. all packages should use a same version of swift-log.

I had the same issue and I went for the approach that shared external dependencies should live in their own package. So the consuming packages just refer to that package and don't manage the versioning themselves.

My approach would be:

  • Stay on the mono repo for a while longer
  • Identify and move out all of the shared external dependencies to their own package
  • Move out the smallest package to a separate remote one (I would guess that is Utils?), pinned on commit
  • Repeat until enough packages are done that you can start sharing the ones you wanted to share
  • Mark the one you wanted to share, plus all of it's dependencies (should be the packages for the external dependencies plus the ones like Utils) with the same version number and publish it
1 Like