# License Service Architecture

> How License Service is split across three separate repositories - the operator (this repo), the operand (the containerized License Service application), and the commons (a shared library) - and how they interplay at build time and runtime. Use to understand which repo owns what, where a change belongs, and how the pieces fit together. Does NOT assume the repos are checked out together or live in the same directory/catalog.

- Skill: `ibm/license-service-architecture` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ibm/license-service-architecture`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ibm/license-service-architecture/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: IBM (https://skillmd.com/u/ibm)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ibm/license-service-architecture

---


# license-service-architecture

License Service is built from **three separate repositories**. This repo,
`ibm-licensing-operator`, is only one of them. Knowing the split tells you **where a change
belongs** and why some behavior can't be changed from here.

> These are independent repositories with their own release cycles. **Do not assume they are
> checked out side by side, in the same parent directory/catalog, or available locally at
> all.** They connect through published artifacts (a container image, a library jar) and
> through the CR/API contract - not through a shared source tree.

## The three repositories

### 1. Operator — `ibm-licensing-operator` (this repo)

- **Language/stack:** Go, built with operator-sdk / kubebuilder.
- **Role:** installs, configures, and manages License Service on a cluster. It owns the
  `IBMLicensing` CRD and the reconcile logic that turns a CR into running Kubernetes objects
  (Deployment, Services, Route/Ingress, ConfigMaps, certificates, RBAC, monitoring).
- **What it does NOT contain:** the License Service application logic itself. The operator
  deploys the operand's **image**; it does not build or import the operand's source.

### 2. Operand — the containerized License Service application

- **Language/stack:** Java / Spring Boot, multi-module Maven, shipped as a multi-arch
  container image.
- **Role:** the actual workload that does the licensing work - collecting, calculating, and
  serving/reporting license-usage data. This is what the operator's Deployment runs.
- **Relationship:** the operator references it **only as a container image** (via the
  licensing image env/config, e.g. `IBM_LICENSING_IMAGE`), pinned per release. Changing what
  License Service *does at runtime* is an operand change, not an operator change.

### 3. Commons — the shared licensing library

- **Language/stack:** a Java library, published as a jar (no container image of its own).
- **Role:** shared domain code (models/POJOs, persistence, shared API types and utilities)
  used by the operand. It is a **build-time dependency of the operand**, consumed as a
  published artifact.
- **Relationship:** the operator (Go) does **not** depend on commons at all.

## How they interplay

```text
   licensing-commons (Java lib, jar)
            │  build-time dependency (published jar)
            ▼
   operand (Java app)  ──build──▶  License Service container image
            ▲                                   │
            │ deploys / configures the image     │ runtime
   ibm-licensing-operator (Go)  ───────────────▶ running License Service Deployment
   (reconciles the IBMLicensing CR)              (+ Services, Route, ConfigMaps, certs)
```

- **Commons → operand** is a **build-time** link: the operand pulls the commons jar as a
  dependency and compiles against it. Nothing in this direction touches the operator.
- **Operator → operand** is a **runtime/deployment** link: the operator never imports operand
  code; it deploys the operand's pre-built image and configures it through the CR spec and
  generated ConfigMaps/env. The contract between them is the **image + its configuration
  surface**, not shared source.
- **Operator ↔ commons:** no relationship. Different language, different toolchain.

The result is loose coupling: the operator and operand are versioned and released
independently, and the operand image is pinned into the operator's manifests per release.

## Where does my change belong?

- **Deploy/config/lifecycle** (CRD fields, how the Deployment/Service/Route/RBAC/monitoring
  are shaped, install flows, Helm/OLM packaging) → **operator** (this repo).
- **License Service behavior** (data collection, calculation, the APIs/endpoints it serves,
  reporting, alerting logic) → **operand** repo.
- **Shared domain models / persistence / shared types** used by the application →
  **commons** repo (then consumed by the operand).

A single user-visible feature can span repos: e.g. exposing a new operand setting usually
means an operand change (implement the behavior) **and** an operator change (surface it on the
`IBMLicensing` CR and wire it into the generated config). Plan such work as coordinated
changes across the relevant repos, released in the right order (commons → operand → operator).

## Working across repos

- Confirm the **operand image version** the operator pins for the release you're on before
  assuming operand behavior; operator and operand advance independently.
- If the other repos aren't available locally, don't fabricate their internals - reason from
  the contract (the CR spec, the image, the published jar) and, when in doubt, say a change
  needs to be made in the operand/commons repo rather than guessing its code.

## Related skills

- [[operator-sdk-guide]] - the internal architecture of **this** repo (the operator).
- [[generate-manifests]] - regenerating CRDs/bundle when the operator's CR/config surface
  changes to expose operand configuration.
- [[build-and-deploy]] - how the operator deploys the operand image locally.

