Application Architecture - Azure/az-prototype GitHub Wiki

Application Architecture

Overview

The application layer (app in the Layer Architecture) is unique among layers because it contains multiple sub-layers that represent distinct tiers of an application. While infrastructure and data layers delegate IaC generation to terraform-agent or bicep-agent, the application layer delegates code generation to language-specific developer agents based on the technology choices made during design.

Application Sub-Layers

Every application stage organizes code into four sub-layers, each in its own directory:

Sub-Layer Purpose Directory
Presentation UI pages, React components, Blazor views, client-side routing web/ or ui/
Domain (Business Logic) Domain models, business rules, validation, workflow orchestration services/ or domain/
Data Access Repository implementations, ORM mappings, query builders data/ or repositories/
Background Background workers, message consumers, scheduled tasks workers/ or functions/

Additionally, cross-cutting concerns live in each project root:

  • Dependency injection configuration (Program.cs / main.py)
  • Structured logging setup
  • Health check endpoints (/healthz)
  • Authentication middleware (MSAL / DefaultAzureCredential)
  • Error handling middleware
  • Configuration binding from environment variables
  • Dockerfile and deployment configuration

Architect-to-Developer Delegation

The application-architect agent owns the entire application layer. It designs the application structure, defines sub-layer boundaries, and then delegates actual code generation to language-specific developer agents.

application-architect
    |
    +-- csharp-developer     (C#/.NET backend + Blazor frontend)
    |
    +-- python-developer     (Python backend)
    |
    +-- react-developer      (React/TypeScript frontend)
    |
    +-- app-developer         (fallback: Java, Go, Rust, etc.)

The architect:

  1. Receives the architecture design and infrastructure outputs
  2. Determines technology choices from the discovery/design context
  3. Defines interface contracts between sub-layers
  4. Assigns each sub-layer to the appropriate developer agent
  5. Ensures dependency injection wires all cross-layer communication

Developer Agent Sub-Layer Contracts

Each developer agent declares which sub-layers it can generate code for via the sub_layers field on its AgentContract:

Agent Sub-Layers Notes
csharp-developer api, business-logic, data-access, background, presentation All 5 -- handles full-stack C#/.NET including Blazor
python-developer api, business-logic, data-access, background 4 backend layers -- no presentation (Python has no frontend framework in scope)
react-developer presentation Presentation only -- frontend communicates with backend via REST API
app-developer (none declared) Generic fallback for unsupported languages (Java, Go, Rust, etc.)
application-architect presentation, api, business-logic, data-access, background All 5 -- can generate directly when no language-specific developer matches

Language Detection and Routing

During the build stage, the _resolve_developer_for_stage() method routes each application stage to the correct developer agent. The routing uses signal-based scoring:

Signal sources (scanned for language hints):

  1. Stage name (e.g., frontend, api-service)
  2. Stage directory path
  3. Service names within the stage
  4. Architecture context lines mentioning the stage

Language hint map:

Hint Resolved Language
csharp, c#, .net, dotnet, aspnet, blazor, entity-framework C#
python, fastapi, flask, django Python
react, typescript, next, angular, vue, frontend, spa React/TypeScript

Scoring process:

  1. Collect text signals from the stage name, directory, service names, and architecture
  2. Count hint matches for each language
  3. Select the language with the highest score
  4. If no hints match (score = 0), fall back to application-architect or app-developer

Delegation flow (_decompose_app_stage):

  1. Call _resolve_developer_for_stage() to detect the language
  2. If a language-specific developer is found, inject sub-layer guidance into the task prompt
  3. If no developer matches, fall back to the application-architect (which handles both design and code generation)

Cross-Layer Dependency Rules

Sub-layers follow strict dependency direction. Each layer depends only on the layer below it, communicating through interfaces and dependency injection:

Presentation
     |  (REST API calls with Bearer token)
     v
API / Controllers
     |  (interface injection)
     v
Domain / Business Logic
     |  (interface injection)
     v
Data Access / Repositories

Rules:

  • API endpoints depend on Domain services via interfaces
  • Domain logic depends on Data Access repositories via interfaces
  • Data Access implements repository interfaces; uses Entity Framework Core, SQLAlchemy, or Azure SDKs
  • Background workers share Domain and Data Access with the API layer
  • Presentation (React) communicates with the backend exclusively via REST API calls -- never accesses Azure services directly
  • Interfaces are defined BEFORE implementations to enable testability and DI
  • All cross-layer wiring happens in the DI configuration entry point (Program.cs / main.py)

Project Structure Examples

C# / ASP.NET Core

src/
+-- MyApp.Api/
|   +-- Program.cs                 # DI config, middleware, host builder
|   +-- MyApp.Api.csproj           # Package references
|   +-- Endpoints/                 # [API] Minimal API endpoint groups
|   +-- Controllers/               # [API] Controllers (if controller-based)
|   +-- Models/                    # [API] Request/response DTOs
|   +-- Services/                  # [Business Logic] Interfaces + implementations
|   +-- Domain/                    # [Business Logic] Domain models, validation
|   +-- Data/                      # [Data Access] DbContext, repositories
|   +-- Middleware/                # [Cross-Cutting] Error handling, auth
|   +-- Extensions/               # [Cross-Cutting] Service registration
|   +-- appsettings.json
|   +-- Dockerfile
|   +-- .env.example
+-- MyApp.Worker/                  # [Background] Worker services
|   +-- Program.cs
|   +-- MyApp.Worker.csproj
|   +-- Consumers/                 # Message consumers
+-- MyApp.Functions/               # [Background] Azure Functions
|   +-- Program.cs                 # Isolated worker host
|   +-- MyApp.Functions.csproj
|   +-- Functions/
+-- MyApp.Shared/
    +-- MyApp.Shared.csproj
    +-- Contracts/                 # Shared interfaces and DTOs

Python / FastAPI

apps/
+-- api/
|   +-- main.py                    # [Cross-Cutting] FastAPI app + DI + middleware
|   +-- config.py                  # [Cross-Cutting] Settings from environment
|   +-- endpoints/                 # [API] Route definitions, request handlers
|   +-- models/                    # [API] Pydantic request/response models
|   +-- services/                  # [Business Logic] Domain logic
|   +-- domain/                    # [Business Logic] Domain models, validation
|   +-- data/                      # [Data Access] Repository pattern, ORM, queries
|   +-- middleware/                # [Cross-Cutting] Error handling, auth
|   +-- requirements.txt
|   +-- Dockerfile
|   +-- .env.example
+-- worker/                        # [Background] Message consumers
|   +-- main.py
|   +-- consumers/
|   +-- requirements.txt
+-- functions/                     # [Background] Azure Functions
|   +-- function_app.py            # v2 programming model
|   +-- requirements.txt
|   +-- host.json
+-- shared/
    +-- contracts.py               # Shared Protocol classes and DTOs
    +-- azure_clients.py           # Azure SDK client factories

React / TypeScript (Presentation Layer)

apps/
+-- web/
    +-- src/
    |   +-- main.tsx                # App entry point
    |   +-- App.tsx                 # Root component with providers
    |   +-- auth/                   # [Auth] MSAL configuration and provider
    |   +-- components/             # [UI] Reusable UI components
    |   |   +-- layout/             #   Layout (Header, Sidebar, Footer)
    |   |   +-- common/             #   Shared (Button, Card, Modal)
    |   |   +-- features/           #   Feature-specific components
    |   +-- pages/                  # [Routing] Route page components
    |   +-- hooks/                  # [State] Custom hooks (useApi, useAuth)
    |   +-- services/               # [API Client] Typed API calls to backend
    |   +-- types/                  # [Contracts] TypeScript interfaces and types
    |   +-- utils/                  # Helper functions
    +-- public/
    +-- index.html
    +-- vite.config.ts
    +-- tsconfig.json
    +-- tailwind.config.js
    +-- package.json
    +-- .env.example
    +-- Dockerfile                  # Multi-stage build (node -> nginx)

Azure Service Authentication

All developer agents enforce the same authentication pattern regardless of language:

  • Backend services use DefaultAzureCredential (C#: Azure.Identity, Python: azure-identity)
  • Frontend (React) uses MSAL (@azure/msal-react) to acquire tokens, sent as Bearer in API calls
  • No secrets are ever hardcoded -- all configuration via environment variables
  • No connection strings with embedded keys -- use managed identity endpoints

See also: Layer Architecture for the full four-level taxonomy, Agent System for complete agent contracts and delegation chains.