1. One Project, Many Deployables: Drawing Boundaries by Runtime Role

    Separate public, admin, batch, and operations workloads into runnable applications without splitting the whole codebase into separate projects too early.

  2. Track Database Changes with the Work That Ships Them

    Collect database changes with the issue and PR that need them, test them in development, and hand off the final release set before code deployment.

  3. Software Is Not Done Until It Is Deployed

    Stale pull requests and delayed releases make changes harder to review, deploy, diagnose, and roll back. Keep the path to production short and observable.

  4. Rebuild First, Migrate Second

    Design a replacement system around the problems it must solve, then handle legacy data through explicit migration mappings and retirement conditions.

  5. Put Logic Where Change Is Cheapest: Client vs. Server

    Place display-only work near the UI, but centralize meaningful or changing values when multiple clients and shipped app versions make updates expensive.

  6. Don't Distort Production Code Just to Make Tests Easier

    Change production code when its behavior becomes clearer or legacy code needs a testing seam—not merely to expose values or methods that only tests use.

  7. Modernize Legacy Systems Gradually—and Treat Caches as Operations

    Choose legacy boundaries carefully, change them in small steps, and add a shared cache only after DB work and deployment failure modes are understood.

  8. Git History Is a Team Asset, Not a Work Diary

    Split work before polishing commits, shape pull requests for reviewers, and leave a history that helps the next engineer understand changes and recover a release.

  9. Upgrade Dependencies Before the Gap Becomes a Project

    Frequent, prioritized dependency upgrades keep change small, expose compatibility problems early, and prevent a maintained service from quietly accumulating debt.