Frequently asked questions.

Question: Are there any plans to add support for cljs/cljc projects, components and bases?

Answer: Yes, we are looking into it here.

Question: Why not only use components and skip the bases?

Answer: They have different responsibilities. If you start to mix the two, you also lose something. A component has an interface, and is composable. A base has no interface but instead, it exposes a public API. The base is the “base” of your projects/artefacts while the components implement the functionality. This makes it easier to reason about our software.

Question: Why use component interfaces instead of protocols as in Stuart Sierra's component?

Answer: The “problem” with a protocol is that you need a common first argument to dispatch on. With Polylith's purely “functional interface” approach, that's not needed: they are just regular functions. Another difference is that protocols live under the same source directory while component interfaces live in their own source directories together with their components and "come to life" only when they are put together into projects. This approach, combined with the monorepo idea, gives an extra level of flexibility compared to protocols.

Question: How is Polylith different than Spring Framework or any other Java framework?

Answer: The two are so different so it’s almost hard to know where to start, but here are the main differences:

  1. Spring is based on an object oriented language that encourages the use of mutable state. Polylith uses stateless functions and encourages the use of immutable state.

  2. Spring uses dependency injection in combination with annotations or a configuration file to “glue” the “building blocks” (objects) together at runtime. Polylith doesn’t use any magic at all, and the way it “glues” the “building blocks” (components and bases) is to specify the source directories for all components and bases in a single file at compile time.

  3. Spring is a framework with a lot of ready-to-use functionality. Polylith is much simpler and doesn’t provide any ready-to-use functionality, but instead it helps us structure the code in a way that we can postpone decisions on how to run our code in production, while maximizing the productivity in our development environment by letting us work with all the code as if it was a single codebase.

Question: Polylith feels a bit like how node modules work in combination with module bundlers like webpack.

Answer: Although it might sound similar to a library (or dependency) solution, such as node modules, Polylith is way more than that. First of all, in contrast to libraries, you are the owner of the Polylith components, they live in the same place as the rest of your (living) code, and they are not frozen as libraries are. They ensure encapsulation and composability but at the same time they are simple, easy to reason about, and flexible. Together with the other concepts introduced with Polylith, it is an opinionated way of architecting software, rather than a dependency system or bundler.

Question: Is it possible to mix programming languages?

Answer: If we want to mix more than one programming language, so that code can be reused across language boundaries, then each language has to live in its own workspace (in one or several repositories). This will work especially well if we run different languages on top of the same platform, e.g. the JVM (see list of JVM languages). We should also pick one of the workspaces to be used when building our artifacts.

Let's say we have the languages, Java, Kotlin and Clojurewhere the latter is the "main" language we use to build our artifacts from. The first thing to remember is to have different names of the top namespaces so that we don't run into name conflicts. In this example, we would end up with top namespaces like: com.mycompany.java, com.mycompany.kotlin and com.mycompany.clojure.

Because we decided to use Clojure as our "main" language, we need to compile the other two as libraries, e.g. java.jar andkotlin.jar .

Question: What parts of Polylith are important and what are just “cermony”? Answer: The short answer is that all parts are needed:

  • interface: Enables encapsulation and functionality to be replaced in projects/artifacts.

  • component: The way we package reusable functionality.

  • base: Enables a public API to be replaced in projects/artifacts.

  • library: Enables global reuse of functionality.

  • project: Enables us to pick and choose what functionality to include in the final artifact.

  • development: Enables us to work with all our code from one place.

  • workspace: Keeps the whole codebase in sync. The standardized naming and directory structure is an example of convention over configuration which enables incremental testing/builds and tooling to be built around Polylith.

Note here that we use the Polylith definitions of these words. For example, an interface in the Polylith world is not the same as an interface in object orientation, and a component in Polylith doesn't need to match the definition of a component in other contexts.

Question: What's your experience of working with Polylith in practice? I would like to hear/read more opinions on Polylith, like from people that have used it in production.

Answer: I (Furkan) am one of the contributors to the Polylith project and I would like to elaborate a little on this. So far, I’ve been involved in four medium-large scale projects that have used Polylith. I’ve recently co-founded a new startup called Scrintal and we’ve written its backend using Polylith. I know that there is a little bit of a learning curve to get used to the Polylith way of thinking. You can think of it as coming to functional programming from OO. However, once you pass that learning phase, you just start focusing on your development, your components specifically, rather than thinking about deployment strategies or architectures. I believe staying productive but at the same time following the current “best practices'' of the software industry is really hard today. It’s mostly because we need to think about how to deploy what we create, before we create it, rather than focusing on being productive. At Scrintal, we are using Clojure and Datomic, which may be considered against the so-called best practice, but it boosts our productivity quite a lot. Having a single REPL where we can try out ideas is great, especially for a startup. You have to move fast and pivot easily. However, you shouldn’t be in a position where you write crap code and once the business takes off, you need to re-think the whole architecture and re-write most of the code. Polylith comes to the rescue. From day one, by creating small building blocks, you can start testing out ideas in your REPL. You can grow your building blocks and add new ones, and grow your codebase that way, but at the same time, Polylith will make sure to keep it simple and untangled. Having small and isolated building blocks ensures that you don’t create a mess in the end. Later on, you can combine all those building blocks in any combination and choose any deployment strategy your product needs. For example, we just had a couple of components in the beginning to test the idea. Later on, we added a simple REST API to deploy it as a single service. After a while, we hit some performance issues and took a couple of components out of the main service to create another service. Still, all the code lived in one single repository and was shared across all services. Polylith allows us to postpone decisions on how to execute our code in production. Instead of making those decisions early when we know the least, we can make them when we hit a problem in production, a non-functional requirement that needs to be fulfilled. Finally, to give a little more context, our backend at Scrintal has around 60 components that are deployed as 4 different services. The very first commit was in December 2019 and we released the first version in July.

Question: Isn't this the Emperor's new clothes in fresh summer styles? I can't see anything new here except that you propose using "libraries"!

Answer: Using libraries was actually how it all began. That was a real pain because it slowed us down significantly. Instead of being able to change the code and get instant feedback from the REPL, we now had to switch between projects, build a snapshot library and restart the REPL. When you do that hundred times per day, it really starts to slow you down. With components, you can work with all your code from one place, using a single REPL. If you zoom in to the different solutions, you will not find anything new here, but when you start working with Polylith, you realize that the separation between development and production actually is a new idea, that components and bases are valuable new concepts and that being able to combine blocks of code in a Lego-like way is very powerful, simple, and makes you more productive in the end. I try to explain all of this in this video.

Question: What's the point of an interface if you can't (or can you? how?) swap out the implementation? (see: OCaml's modules)

Answer: Each component contains its own interface file/namespace. If two components are using the same interface, the contract of the interface is the combined set of def/defn/defmacro definitions for both components. If any of them don’t implement the full set, then the tool will complain when running the check, info or test command.

We have an example in the Profile section of the tool documentation, where both the user and user-remote components implement the user interface. The components live in two separate directories, under the components directory, and both use the se.example.user namespace but with different implementations in their core namespace:

▾ workspace
  ▾ components
    ▾ user
    ▾ user-remote

The example starts with a command-line project that contains the user component, but then we “swap” (at compile time) to the user-remote component, by specifying the source directory of user-remote instead of user in projects/command-line/deps.edn. This is described in detail in the Profile section of the tool documentation.

Question: If I have two bases (say http-api and mq-api), can I easily configure the build to produce a single artifact, that includes both bases?

Answer: Yes, you include the bases in your deps.edn configuration file for the project, e.g.: {:deps {poly/http-api {:local/root “../../bases/http-api/src”} poly/mq-api {:local/root “../../bases/mq-api/src”} ... }. You can put any combination of components and bases in a project and build a single artifact out of it. We don’t support switching components at run-time. If you need polymorphism, then you can solve it by using multi-methods to switch between two different components.

Question: Aren't "pass-through" functions used in the interfaces kinda stupid, when you instead can use import-vars to "import" them?

Answer: The import-vars macro is kind of cool, but we have decided to keep it as it is. The main reason is that consistency and simplicity have a great value to us. Using a macro could have been an alternative if it solved the whole problem, but unfortunately, we will end up with a mix of this macro and explicitly declared functions, which is less consistent and adds complexity. By making the def/defn/defmacro statements explicit in the interface namespace(s) we also get a lot of flexibility, see the end of the interface section of the tool documentation.

Question: How to design good interfaces? Answer: Polylith gives you the “tool” here, but it’s up to you to decide what is a good or bad interface. Our best advice here is that you have a look at the realworld example app and the Polylith codebase itself to get some answers/inspiration.

Question: How to grow and extend interfaces? Answer: We normally add one more function at a time to an interface when we need more functionality. We also change the name of a function when we found a better name. We use different techniques to improve the readability of the interface which you can read about at the end of the interface section. When a part within a component can be used somewhere else, we extract it to a new component to get rid of code duplication. In that case, the functions that previously lived in the first component’s interface will now live in the new one. In general, try to communicate what the interface does and/or is, as clearly as possible.

Question: Does Polylith allow you to upgrade a system while it’s running? Answer: Polylith doesn’t help you with that. Polylith helps you with a lot like separating development from production, but it’s not what e.g. Spring is for Java.

Question: How to handle state? Answer: The short answer is that this is also handled by you as a developer, by using an existing library or tool. This is explained In the profile section.

Question: When we program we want to structure the code into “difficult” and “easy” modules. The difficult modules should be few and written by expert programmers. The easy modules should be many and written by less experienced programmers. How does Polylith allow this? Answer: You are free to organise your components, bases, and projects in any way you like in Polylith. Because it’s so easy to refactor a Polylith codebase, it’s also easy to adjust the design while you go, without painting yourself into a corner. If you prefer to divide the codebase into “difficult” and “easy” components, that’s fine, but we don’t have strong opinions about this, because people have different perspectives on what is good or bad practice/design.

Last updated