Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebalance the dependency tree by moving & tossing the modules #765

Merged
merged 2 commits into from
May 11, 2021

Conversation

nolar
Copy link
Owner

@nolar nolar commented May 11, 2021

TL;DR: Rebalance the dependency tree by moving & tossing the modules.

There are no changes in the behaviour of the framework or the public interfaces. Some preparatory changes were made as separate PRs (for semantic grouping and easier investigations in the future; in some PRs, there were minor changes of public interfaces (e.g. for daemon stopping flags)):

#751 #752 #756 #757+#760 #759 #761 #763 #764.

The time is right: after it is feature-complete and most likely will no be expanding in complexity, but before it might get widely adopted and contributions will flow (we can dream, [slowly] we… can… dream…).

Problem

Background: In the beginning, several decisions were made to structure the framework to two primary packages — reactor and structs — and keep them layered inside. Eventually, this decision forced all data structures to be placed in structs even if they belonged to some modules/aspects/functionality of reactor — to not violate layering. As a result, this broke cohesion and made almost all reactor aspects "mirrored" in structs with no particular reason except for dependencies, while both reactor & structs themselves were exploding in complexity.

Goal: The goal is to rebalance the dependency tree: specifically, to increase cohesion and reduce the complexity of layers.

Solution

AS IS: Before the change, there were these essential layers:

  • kopf.toolkits, kopf.on, kopf -- user-facing API.
  • kopf.reactor.
  • kopf.engines.
  • kopf.structs.
  • kopf.storage.
  • kopf.aiokits (only since recently).
  • kopf.clients -- low-level K8s API communication.
  • kopf.utilities -- system- & language-level helpers and adapters.

TO BE: After the change, the layered layout is this:

  • kopf, kopf.on, kopf.testing — public interfaces (can be imported & accessed).
  • kopf._kits — the user-facing helpers (not used in the framework itself).
  • kopf._core — the high-level essentials of the framework:
    • kopf._core.reactor — the central unit that runs and reacts to resource changes.
    • kopf._core.engines — specialised aspects/features of the framework.
    • kopf._core.intents — operator- & reaction-defining structures (causes & effects).
    • kopf._core.actions — functions & handler invocation with dynamic kwargs.
  • kopf._cogs — the low-level helpers & glue-code of the framework:
    • kopf._cogs.clients — the Kubernetes API communication.
    • kopf._cogs.configs — the settings & persistence storage classes.
    • kopf._cogs.structs — reusable simple(!) data structures and types.
    • kopf._cogs.aiokits — asynchronous primitives and patterns.
    • kopf._cogs.helpers — system- & language-level helpers and adapters.

The specific modules are moved across these packages for better balance and cohesion.

A rough criterion of a good balance is ~7 modules per package; the imports should be one-liners again.

Side-goals

Besides, a few side-goal were achieved:

Hidden packages: While moving the modules, those considered internal are now hidden behind underscore-named packages, so that any attempt to import them would raise concerns that they are not part of the public API and can change at any time. Previously, modules like kopf.clients.patching.patch_obj() could be mistakenly perceived as public too.

Import linter: Automated import linter is added, which ensures that the modules are layered accordingly and no violation (e.g. reversed imports of upper layers to lower levels) is going to happen in the future.

@nolar nolar added the refactoring Code cleanup without new features added label May 11, 2021
There are no changes in the behaviour of the framework or the public interfaces. Some preparatory changes were made as separate PRs (for semantic grouping and easier investigations in the future; in some PRs, there were minor changes of public interfaces (e.g. for daemon stopping flags)).

## Problem

**Background:** In the beginning, several decisions were made to structure the framework to two primary packages — `reactor` and `structs` — and keep them layered inside. Eventually, this decision forced all data structures to be placed in `structs` even if they belonged to some modules/aspects/functionality of `reactor` — to not violate layering. As a result, this broke cohesion and made almost all `reactor` aspects "mirrored" in `structs` with no particular reason except for dependencies, while both `reactor` & `structs` themselves were exploding in complexity.

**Goal:** The goal is to rebalance the dependency tree: specifically, to increase cohesion and reduce the complexity of layers.

## Solution

**AS IS:** Before the change, there were these essential layers:

* `kopf.toolkits`, `kopf.on`, `kopf` -- user-facing API.
* `kopf.reactor`.
* `kopf.engines`.
* `kopf.structs`.
* `kopf.storage`.
* `kopf.aiokits` (only since recently).
* `kopf.clients` -- low-level K8s API communication.
* `kopf.utilities` -- system- & language-level helpers and adapters.

**TO BE:** After the change, the layered layout is this:

* `kopf`, `kopf.on`, `kopf.testing` — public interfaces (can be imported & accessed).
* `kopf._kits` — the user-facing helpers (not used in the framework itself).
* `kopf._core` — the **high-level** essentials of the framework:
  * `kopf._core.reactor` — the central unit that runs and reacts to resource changes.
  * `kopf._core.engines` — specialised aspects/features of the framework.
  * `kopf._core.intents` — operator- & reaction-defining structures (causes & effects).
  * `kopf._core.actions` — functions & handler invocation with dynamic kwargs.
* `kopf._cogs` — the **low-level** helpers & glue-code of the framework:
  * `kopf._cogs.clients` — the Kubernetes API communication.
  * `kopf._cogs.configs` — the settings & persistence storage classes.
  * `kopf._cogs.structs` — reusable simple(!) data structures and types.
  * `kopf._cogs.aiokits` — asynchronous primitives and patterns.
  * `kopf._cogs.helpers` — system- & language-level helpers and adapters.

The specific modules are moved across these packages for better balance and cohesion.

A rough criterion of a good balance is ~7 modules per package; the imports should be one-liners again.

## Side-goals

Besides, a side-goal was achieved:

**Hidden packages:** While moving the modules, those considered internal are now hidden behind underscore-named packages, so that any attempt to import them would raise concerns that they are not part of the public API and can change at any time. Previously, modules like `kopf.clients.patching.patch_obj()` could be mistakenly perceived as public too.

Signed-off-by: Sergey Vasilyev <nolar@nolar.info>
Signed-off-by: Sergey Vasilyev <nolar@nolar.info>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactoring Code cleanup without new features added
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant