Skip to content

Latest commit

Β 

History

History
437 lines (341 loc) Β· 11.5 KB

File metadata and controls

437 lines (341 loc) Β· 11.5 KB

πŸ“¦ Monorepo Structure Guide

Understanding and working with Discogsography's uv workspace-based monorepo

Overview

Discogsography uses a monorepo structure with uv workspaces, allowing multiple services to share code while maintaining independent dependencies. This guide explains how to effectively work within this structure.

πŸ—οΈ Repository Structure

graph TD
    Root[discogsography/<br/>Root Workspace]

    RootFiles[pyproject.toml<br/>uv.lock<br/>.python-version]

    API[api/<br/>API Service]
    APIFiles[api.py<br/>routers/]

    Common[common/<br/>Shared Library]
    CommonFiles[config.py<br/>health_server.py]

    Dashboard[dashboard/<br/>Monitoring Service]
    DashFiles[dashboard.py<br/>static/]

    Extractor[extractor/<br/>Data Extraction]
    ExtractorFiles[src/main.rs<br/>Cargo.toml]

    Explore[explore/<br/>Static File Serving]
    ExploreFiles[explore.py]

    Graphinator[graphinator/<br/>Neo4j Service]
    GraphFiles[graphinator.py]

    SchemaInit[schema-init/<br/>DB Schema Init]
    SchemaFiles[neo4j_schema.py<br/>postgres_schema.py]

    Tableinator[tableinator/<br/>PostgreSQL Service]
    TableFiles[tableinator.py]

    BrainzGraph[brainzgraphinator/<br/>MusicBrainz Neo4j]
    BrainzGraphFiles[brainzgraphinator.py]

    BrainzTable[brainztableinator/<br/>MusicBrainz PostgreSQL]
    BrainzTableFiles[brainztableinator.py]

    Insights[insights/<br/>Analytics Service]
    InsightsFiles[insights.py<br/>computations.py]

    MCPServer[mcp-server/<br/>AI Assistant MCP]
    MCPFiles[server.py]

    Root --> RootFiles
    Root --> API
    Root --> Common
    Root --> Dashboard
    Root --> Extractor
    Root --> Explore
    Root --> Graphinator
    Root --> SchemaInit
    Root --> Tableinator
    Root --> BrainzGraph
    Root --> BrainzTable
    Root --> Insights
    Root --> MCPServer

    API --> APIFiles
    Common --> CommonFiles
    Dashboard --> DashFiles
    Extractor --> ExtractorFiles
    Explore --> ExploreFiles
    Graphinator --> GraphFiles
    SchemaInit --> SchemaFiles
    Tableinator --> TableFiles
    BrainzGraph --> BrainzGraphFiles
    BrainzTable --> BrainzTableFiles
    Insights --> InsightsFiles
    MCPServer --> MCPFiles

    style Root fill:#f3e5f5,stroke:#9c27b0,stroke-width:3px
    style API fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style Common fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style Dashboard fill:#e3f2fd,stroke:#2196f3,stroke-width:2px
    style Extractor fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style Explore fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style Graphinator fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
    style SchemaInit fill:#f1f8e9,stroke:#689f38,stroke-width:2px
    style Tableinator fill:#e0f2f1,stroke:#009688,stroke-width:2px
    style BrainzGraph fill:#e0f7fa,stroke:#00838f,stroke-width:2px
    style BrainzTable fill:#fff8e1,stroke:#ff8f00,stroke-width:2px
    style Insights fill:#fff9c4,stroke:#f57f17,stroke-width:2px
    style MCPServer fill:#ede7f6,stroke:#5e35b1,stroke-width:2px
Loading

Directory Structure Details

discogsography/                    # Root workspace
β”œβ”€β”€ pyproject.toml                 # Root configuration & shared dev dependencies
β”œβ”€β”€ uv.lock                        # Single lock file for entire workspace
β”œβ”€β”€ .python-version                # Python version for uv
β”‚
β”œβ”€β”€ api/                           # API service (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml            # Service-specific dependencies
β”‚   β”œβ”€β”€ api.py                    # Service entry point
β”‚   └── routers/                  # FastAPI routers
β”‚
β”œβ”€β”€ common/                        # Shared library (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml            # Declares [project] with name
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ config.py                 # Shared configuration
β”‚   └── health_server.py          # Shared health check server
β”‚
β”œβ”€β”€ dashboard/                     # Service (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml           # Service-specific dependencies
β”‚   β”œβ”€β”€ dashboard.py             # Service entry point
β”‚   └── static/                  # Service assets
β”‚
β”œβ”€β”€ extractor/                     # Data extraction service (Rust β€” not a uv workspace member)
β”‚   β”œβ”€β”€ Cargo.toml                # Rust dependencies
β”‚   └── src/
β”‚       └── main.rs              # Rust entry point
β”‚
β”œβ”€β”€ explore/                       # Static file serving (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml            # Service dependencies
β”‚   └── explore.py                # Service entry point
β”‚
β”œβ”€β”€ graphinator/                   # Service (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml           # Neo4j dependencies
β”‚   └── graphinator.py
β”‚
β”œβ”€β”€ brainzgraphinator/             # MusicBrainz Neo4j enrichment (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml           # Service dependencies
β”‚   └── brainzgraphinator.py
β”‚
β”œβ”€β”€ brainztableinator/             # MusicBrainz PostgreSQL storage (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml           # Service dependencies
β”‚   └── brainztableinator.py
β”‚
β”œβ”€β”€ insights/                      # Precomputed analytics (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml           # Service dependencies
β”‚   β”œβ”€β”€ insights.py              # Service entry point
β”‚   └── computations.py          # Computation orchestration
β”‚
β”œβ”€β”€ mcp-server/                    # AI assistant MCP server (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml           # Service dependencies
β”‚   └── server.py                # FastMCP server
β”‚
β”œβ”€β”€ schema-init/                   # Database schema initializer (workspace member)
β”‚   β”œβ”€β”€ pyproject.toml           # Schema dependencies
β”‚   β”œβ”€β”€ neo4j_schema.py          # Neo4j constraints & indexes
β”‚   └── postgres_schema.py       # PostgreSQL tables
β”‚
└── tableinator/                   # Service (workspace member)
    β”œβ”€β”€ pyproject.toml           # PostgreSQL dependencies
    └── tableinator.py

πŸ”‘ Key Concepts

1. Workspace Members

Each service is a workspace member with its own pyproject.toml:

# dashboard/pyproject.toml
[project]
name = "dashboard"
version = "1.0.0"
dependencies = [
    "fastapi>=0.115.6",
    "websockets>=14.1",
    # Service-specific deps
]

2. Shared Dependencies

The root pyproject.toml defines:

  • Development dependencies (testing, linting)
  • Tool configurations (ruff, mypy, pytest)
  • Workspace member declarations
# Root pyproject.toml
[tool.uv.workspace]
members = [
    "api",
    "brainzgraphinator",
    "brainztableinator",
    "common",
    "dashboard",
    "explore",
    "graphinator",
    "insights",
    "mcp-server",
    "schema-init",
    "tableinator"
]

[project.optional-dependencies]
dev = [
    "pytest>=8.3.4",
    "ruff>=0.8.6",
    "mypy>=1.14.1",
    # Shared dev tools
]

3. Single Lock File

  • One uv.lock at the root locks ALL dependencies
  • Ensures version consistency across services
  • Updated automatically by uv

πŸ“‹ Common Tasks

Installing Dependencies

# Install everything (recommended for development)
uv sync --all-extras

# Install specific service dependencies
uv sync --extra dashboard

# Install only dev dependencies
uv sync --extra dev

Adding Dependencies

# Add to specific service
cd dashboard
uv add fastapi  # Adds to dashboard/pyproject.toml

# Add dev dependency (from root)
uv add --dev pytest-asyncio

# Add with version constraint
uv add "neo4j>=5.15.0"

Running Services

# From project root (recommended)
uv run python dashboard/dashboard.py
uv run python explore/explore.py

# Using just commands
just dashboard
just extractor

Import Patterns

# Services can import from common
from common.config import Config
from common.health_server import HealthServer


# Or from explore
from common.health_server import HealthServer
# But NOT from other services (bad practice)
# from dashboard.something import thing  # ❌ Don't do this

πŸ› οΈ Development Workflow

1. Making Changes to Shared Code

When modifying common/:

# 1. Make your changes in common/
edit common/config.py

# 2. No reinstall needed - changes are immediate
# (common is installed in editable mode)

# 3. Test affected services
just test

2. Adding a New Service

# 1. Create service directory
mkdir myservice
cd myservice

# 2. Create pyproject.toml
cat > pyproject.toml << EOF
[project]
name = "myservice"
version = "1.0.0"
dependencies = [
    # Service dependencies
]
EOF

# 3. Add to root pyproject.toml
# Edit [tool.uv.workspace] members list

# 4. Sync workspace
cd ..
uv sync

3. Service-Specific Testing

# Test single service
uv run pytest tests/dashboard/

# Test with coverage
uv run pytest tests/dashboard/ --cov=dashboard

# Test everything
just test

⚠️ Common Pitfalls

1. Wrong Directory

# ❌ Bad: Running from service directory
cd dashboard
uv run python dashboard.py  # May fail to find imports

# βœ… Good: Running from root
uv run python dashboard/dashboard.py

2. Manual Dependency Edits

# ❌ Bad: Editing pyproject.toml manually
# Can break uv.lock consistency

# βœ… Good: Using uv commands
uv add package-name

3. Cross-Service Imports

# ❌ Bad: Services importing from each other
# dashboard/dashboard.py
from dashboard.something import thing

# βœ… Good: Only import from common
from common.config import Config

4. Installing Without Workspace

# ❌ Bad: Installing service in isolation
cd dashboard
pip install -e .

# βœ… Good: Using uv sync from root
uv sync --all-extras

🐳 Docker Considerations

Each service has its own Dockerfile but shares the workspace structure:

# Copy entire workspace
COPY pyproject.toml uv.lock ./
COPY common ./common
COPY dashboard ./dashboard

# Install specific service
RUN uv sync --frozen --no-dev --extra dashboard

🎯 Best Practices

  1. Always Work from Root: Run commands from project root
  2. Use uv Commands: Don't edit dependency files manually
  3. Shared Code in common/: Put reusable code here
  4. Service Independence: Services shouldn't depend on each other
  5. Test After Changes: Especially when modifying common/
  6. Keep Services Focused: Each service should have a single responsibility

πŸ“Š Workspace Benefits

Benefit Description
Code Reuse Share utilities via common/
Version Consistency Single lock file prevents conflicts
Faster Installation uv caches dependencies efficiently
Atomic Updates Update all services simultaneously
Simplified CI/CD One install step for everything

πŸ” Debugging

Check Workspace Members

# Show dependency tree
uv tree

# List installed packages
uv pip list

Verify Installation

# Check if service is installed
uv run python -c "import dashboard; print(dashboard.__file__)"

Force Reinstall

# Clean install
rm -rf .venv
uv sync --all-extras

πŸ“š Further Reading