# Polylith Migrate Isolate Base And Big Component

> [Internal sub-skill of `polylith-migrate-orchestrator`. Do not load directly — load `polylith-migrate-orchestrator` first, which drives all phases.] Shrink the temporary migration base into thin base(s) + one big component. Bases contain only entrypoints/wiring, while the big component contains everything else.

- Skill: `davidvujic/polylith-migrate-isolate-base-and-big-component` (Agent Skill)
- Install (CLI): `npx skillmds@latest add davidvujic/polylith-migrate-isolate-base-and-big-component`
- Raw SKILL.md: https://api.skillmd.com/api/skills/davidvujic/polylith-migrate-isolate-base-and-big-component/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: davidvujic (https://skillmd.com/u/davidvujic)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/davidvujic/polylith-migrate-isolate-base-and-big-component

---


# Skill: polylith-migrate-isolate-base-and-big-component

## Goal
Shrink the temporary migration base into thin base(s) + one big component:
- **Bases**: Contain only entrypoints/wiring (e.g., FastAPI endpoints, CLI wiring, consumer wiring).
- **Big Component**: Contains all other code.

> ⚠ **Base vs component naming (read first).** A base and a component **cannot share
> the same brick name** — both would resolve to the Python package `<TARGET_TOP_NS>.<name>`
> and collide. So the **big component takes `INITIAL_BASE_NAME`**
> (`<TARGET_TOP_NS>.<INITIAL_BASE_NAME>`) and each **base is renamed** to a distinct,
> entrypoint-specific name (the `<base>` used in step 5 — e.g. `<INITIAL_BASE_NAME>_api`,
> `<INITIAL_BASE_NAME>_handler`). This is *why* the temporary base must be renamed when
> the component claims its name.
>
> **Multiple entrypoints → multiple thin bases.** A real service often has several
> deployable entrypoints (an HTTP API + one or more queue consumers + scheduled jobs).
> Create **one thin base per entrypoint** (e.g. `<x>_api`, `<x>_handler`, `<x>_jobs`),
> each exposing only its run/wiring function and importing logic from components. Do
> **not** force them into a single base. (Shared startup wiring — `init_db`,
> `init_logging`, … — goes into a small shared component, not duplicated per base; see
> `polylith-migrate-distribute-wiring`.)

## Inputs
From `migration/<PROJECT>/state.md`:
- `TARGET_TOP_NS`
- `INITIAL_BASE_NAME`
- Verification commands.

From `migration/<PROJECT>/manifest.md`:
- Entrypoints list.

> All inputs from `state.md` are assumed to satisfy the validation rules in `polylith-migrate-discover` (`### Validation rules`). Validate before proceeding.

## Steps

### 1. Create the Big Component
- Create `components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/`.
- ⚠ If the temporary base currently occupies `bases/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/`, **rename the base(s) first** — they must not keep the same name as the big component (see the naming callout above). Create one renamed thin base per entrypoint (`<base>` in step 5), and update the project's run scripts / Dockerfile to point at the new base name(s).

### 2. Move Non-Entrypoint Code
- Move non-entrypoint code from the base(s) to the big component.

### 3. Define Public API
- Define a minimal public API in `components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/__init__.py`.

### 4. Update Bases
- Update bases to import only from component APIs:
  ```python
  from <TARGET_TOP_NS>.<INITIAL_BASE_NAME> import ...
  ```

### 5. Update `pyproject.toml`
- Add the new component to `[tool.polylith.bricks]`:
  ```toml
  [tool.polylith.bricks]
  "../../bases/<TARGET_TOP_NS>/<base>" = "<TARGET_TOP_NS>/<base>"
  "../../components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>" = "<TARGET_TOP_NS>/<INITIAL_BASE_NAME>"
  ```

### 6. Update `manifest.md`
- Reflect the new structure in `migration/<PROJECT>/manifest.md`.

### 7. FastAPI Guidance
| Stays in Base | Moves to Big Component |
|---------------|------------------------|
| `app = FastAPI(...)` | Domain/business logic |
| Middleware, router registration | Persistence/repositories |
| Route handlers (endpoints) | External integrations |
| Startup/shutdown/lifespan wiring | Reusable parsing/validation |

## Verify
- `RUN_TEST_CMD` succeeds.
- If set, `RUN_LINT_CMD` and `RUN_TYPECHECK_CMD` succeed.
- Run `POLY_CMD_PREFIX check` to validate the workspace structure.
- Run `POLY_CMD_PREFIX sync` to synchronize the `[tool.polylith.bricks]` table with actual imports.

## Common failure modes

| Symptom | Likely cause | Remediation |
|---------|--------------|-------------|
| Circular import: base imports from component, component imports back from base | Some "non-entrypoint" code was moved but still references base-only helpers (e.g. the FastAPI `app` instance). | Move the helper down into the component, or invert the dependency by passing the needed value as a function argument. The base's `app` instance must **never** be imported by a component. |
| `poly check` / import error: base and big component both named `<INITIAL_BASE_NAME>` | The base wasn't renamed when the component claimed `INITIAL_BASE_NAME` — two bricks now map to `<TARGET_TOP_NS>.<INITIAL_BASE_NAME>`. | Rename the base to an entrypoint-specific name (`<INITIAL_BASE_NAME>_api`, `_handler`, …), update `[tool.polylith.bricks]` and the run scripts, then `POLY_CMD_PREFIX sync`. |
| `ImportError: cannot import name '<x>' from '<TARGET_TOP_NS>.<INITIAL_BASE_NAME>'` | The big component's `__init__.py` doesn't re-export `<x>`. Code that previously reached into submodules now needs the public API. | Add `from <TARGET_TOP_NS>.<INITIAL_BASE_NAME>.<submodule> import <x>` to the component's `__init__.py`, or have the caller import the submodule directly (and accept the brick-interface violation that `poly deps --interface` will flag). |
| Base file becomes near-empty after the split | Good — that's the goal. But check: is there *any* wiring left, or did you accidentally move the entrypoint itself? | If the entrypoint (e.g., `app = FastAPI(...)`) ended up in the component, move it back to the base. The base must own the entrypoint object. |
| `poly check` flags the component as not used by any project | The base's imports go to the wrong namespace (e.g., `from <ORIG_TOP_NS>...`) so the import graph doesn't reach the component. | Update base imports to `from <TARGET_TOP_NS>.<INITIAL_BASE_NAME> import …` and re-run `POLY_CMD_PREFIX sync`. |
| Tests for moved code now fail to find fixtures | `conftest.py` was left in the base or moved to the wrong scope. | Move test fixtures alongside the code they cover; usually that's under `test/components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/`. |
| Verification fails and you can't quickly diagnose | Phase commit not yet made. | `git reset --hard HEAD` to roll back to the previous phase's commit and consult the user. |

## Commit

After verification passes, commit this phase to the migration branch:

```bash
git add -A && git commit -m "migrate(<PROJECT>): phase <N> — isolate-base-and-big-component"
```

Substitute `<PROJECT>`, `<N>`, and `<phase-name>` from `state.md` and the orchestrator's phase table. Do not proceed to the next phase without a clean commit — the per-phase commit is the rollback point for the next phase's failure-mode tables.

