ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-03-31Advanced

Antigravity Clean Architecture × DDD Implementation Guide

Implement Clean Architecture and Domain-Driven Design with Antigravity. Master layer separation, repository patterns, use case design, and automated architecture analysis using AI agents

antigravity435clean-architectureddddesign-patterns2repository-patterndependency-injectionproduction71

Setup and context — Why Clean Architecture and DDD Matter

As projects grow in complexity, code becomes increasingly difficult to understand and modify. A codebase that "just works" inevitably accumulates technical debt, making new feature development and bug fixes progressively slower — a reality most development teams face.

Clean Architecture and Domain-Driven Design (DDD) provide powerful solutions to this challenge. However, understanding the principles isn't enough. Implementation often reveals tangled dependencies that pull code away from the original design.

This is where Antigravity's AI agents excel. They can validate architecture, propose refactorings, and even auto-fix violations — helping you maintain production-grade architecture as your project scales.


Clean Architecture Fundamentals

The Four-Layer Structure

Clean Architecture consists of concentric layers, each with a specific responsibility:

1. Entity Layer (Domain Layer)

  • Houses core business logic and domain models
  • Independent of frameworks, databases, and UI
  • Changes least frequently
  • Contains pure business rules

2. Use Case Layer (Application Layer)

  • Implements application-specific business logic
  • Orchestrates entities and external systems
  • Defines "how to achieve business goals"
  • Coordinates workflows across domain entities

3. Interface Adapter Layer

  • Web frameworks (Express, Next.js, etc.)
  • Repository implementations
  • External API clients
  • Acts as a "translator" between inner and outer layers

4. Frameworks and Tools Layer

  • Web framework internals
  • Database drivers
  • Caching, messaging infrastructure
  • Third-party libraries

The Dependency Rule

Clean Architecture's most critical principle: all dependencies point inward (toward the center).

Outer layers can depend on inner layers, but inner layers must never depend on outer layers.

// ✓ Allowed dependency directions
// Use Case Layer → Entity Layer
// Adapter Layer → Use Case Layer → Entity Layer
 
// ✗ Forbidden dependency directions
// Entity Layer → Use Case Layer
// Entity Layer → Adapter Layer

This achieves:

  • Resilience to external change: Framework changes don't affect business logic
  • Testability: Business logic can be tested without frameworks
  • Reusability: Same business logic works across multiple interfaces (Web, CLI, API)

Connection to SOLID Principles

Clean Architecture is a concrete realization of SOLID principles:

  • S (Single Responsibility): Each class has one reason to change
  • O (Open/Closed): Open for extension, closed for modification
  • L (Liskov Substitution): Subtypes can replace their base types
  • I (Interface Segregation): Clients depend only on needed methods
  • D (Dependency Inversion): Depend on abstractions, not concretions

Project Structure Design

Optimal Directory Organization

src/
├── domain/                    # Entity Layer
│   ├── entities/
│   │   ├── User.ts
│   │   ├── Order.ts
│   │   └── Product.ts
│   ├── value-objects/
│   │   ├── Email.ts
│   │   ├── Money.ts
│   │   └── UserId.ts
│   ├── repositories/
│   │   ├── IUserRepository.ts
│   │   ├── IOrderRepository.ts
│   │   └── IProductRepository.ts
│   └── services/
│       ├── PricingDomainService.ts
│       └── InventoryDomainService.ts
│
├── application/               # Use Case Layer
│   ├── use-cases/
│   │   ├── CreateOrderUseCase.ts
│   │   ├── UpdateUserProfileUseCase.ts
│   │   └── GetProductDetailsUseCase.ts
│   ├── dto/
│   │   ├── CreateOrderRequest.ts
│   │   ├── CreateOrderResponse.ts
│   │   └── GetProductResponse.ts
│   ├── services/
│   │   ├── OrderApplicationService.ts
│   │   └── UserApplicationService.ts
│   └── ports/
│       ├── IEmailService.ts
│       ├── IPaymentGateway.ts
│       └── INotificationService.ts
│
├── infrastructure/            # Adapter & Tools Layer
│   ├── repositories/
│   │   ├── UserRepository.ts
│   │   ├── OrderRepository.ts
│   │   └── ProductRepository.ts
│   ├── external-services/
│   │   ├── StripePaymentGateway.ts
│   │   ├── SendgridEmailService.ts
│   │   └── TwilioNotificationService.ts
│   ├── persistence/
│   │   ├── prisma/
│   │   └── schema.prisma
│   └── config/
│       ├── database.ts
│       └── dependencies.ts
│
├── presentation/              # Controllers & Handlers Layer
│   ├── http/
│   │   ├── controllers/
│   │   │   ├── OrderController.ts
│   │   │   └── UserController.ts
│   │   └── middleware/
│   │       ├── auth.ts
│   │       └── errorHandler.ts
│   └── cli/
│       └── commands/
│           └── MigrateCommand.ts
│
└── shared/                    # Shared Utilities
    ├── errors/
    │   ├── DomainError.ts
    │   └── ApplicationError.ts
    ├── utils/
    │   ├── Logger.ts
    │   └── Validator.ts
    └── types/
        └── index.ts

Module Separation Strategy

For large projects, organize around business domains (Bounded Contexts):

src/
├── modules/
│   ├── user/
│   │   ├── domain/
│   │   ├── application/
│   │   ├── infrastructure/
│   │   └── presentation/
│   ├── order/
│   │   ├── domain/
│   │   ├── application/
│   │   ├── infrastructure/
│   │   └── presentation/
│   └── payment/
│       ├── domain/
│       ├── application/
│       ├── infrastructure/
│       └── presentation/

Each module implements the four-layer architecture independently. Inter-module communication happens only through public interfaces.


Domain Layer Implementation

Entity Design

Entities are domain objects with unique identifiers. While attributes change over time, the identity remains constant.

// User entity
import { UserId } from './value-objects/UserId';
import { Email } from './value-objects/Email';
 
export class User {
  private userId: UserId;
  private email: Email;
  private name: string;
  private age: number;
  private createdAt: Date;
  private updatedAt: Date;
 
  constructor(
    userId: UserId,
    email: Email,
    name: string,
    age: number,
    createdAt: Date = new Date(),
    updatedAt: Date = new Date()
  ) {
    this.validate(name, age);
    this.userId = userId;
    this.email = email;
    this.name = name;
    this.age = age;
    this.createdAt = createdAt;
    this.updatedAt = updatedAt;
  }
 
  private validate(name: string, age: number): void {
    if (!name || name.trim().length === 0) {
      throw new Error('User name must not be empty');
    }
    if (age < 0 || age > 150) {
      throw new Error('User age must be between 0 and 150');
    }
  }
 
  // Business logic: age validation
  isAdult(): boolean {
    return this.age >= 18;
  }
 
  // Business logic: update email
  changeEmail(newEmail: Email): void {
    this.email = newEmail;
    this.updatedAt = new Date();
  }
 
  // Business logic: update profile
  updateProfile(name: string, age: number): void {
    this.validate(name, age);
    this.name = name;
    this.age = age;
    this.updatedAt = new Date();
  }
 
  // Getters
  getId(): UserId {
    return this.userId;
  }
 
  getEmail(): Email {
    return this.email;
  }
 
  getName(): string {
    return this.name;
  }
 
  getAge(): number {
    return this.age;
  }
 
  getCreatedAt(): Date {
    return this.createdAt;
  }
 
  getUpdatedAt(): Date {
    return this.updatedAt;
  }
}

Value Objects

Value objects lack identity and are distinguished by their values. Examples include email addresses, money amounts, and physical addresses.

// Email value object
export class Email {
  private readonly value: string;
 
  constructor(value: string) {
    if (!this.isValid(value)) {
      throw new Error(`Invalid email format: ${value}`);
    }
    this.value = value;
  }
 
  private isValid(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
 
  getValue(): string {
    return this.value;
  }
 
  equals(other: Email): boolean {
    return this.value === other.value;
  }
 
  toString(): string {
    return this.value;
  }
}
 
// Money value object
export class Money {
  private readonly amount: number;
  private readonly currency: string;
 
  constructor(amount: number, currency: string = 'USD') {
    if (amount < 0) {
      throw new Error('Money amount must not be negative');
    }
    if (!/^[A-Z]{3}$/.test(currency)) {
      throw new Error('Currency must be a valid ISO 4217 code');
    }
    this.amount = amount;
    this.currency = currency;
  }
 
  getAmount(): number {
    return this.amount;
  }
 
  getCurrency(): string {
    return this.currency;
  }
 
  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('Cannot add money in different currencies');
    }
    return new Money(this.amount + other.amount, this.currency);
  }
 
  multiply(factor: number): Money {
    return new Money(this.amount * factor, this.currency);
  }
 
  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }
 
  toString(): string {
    return `${this.currency} ${this.amount.toFixed(2)}`;
  }
}
 
// UserId value object
export class UserId {
  private readonly value: string;
 
  constructor(value: string) {
    if (!value || value.trim().length === 0) {
      throw new Error('UserId must not be empty');
    }
    this.value = value;
  }
 
  getValue(): string {
    return this.value;
  }
 
  equals(other: UserId): boolean {
    return this.value === other.value;
  }
 
  toString(): string {
    return this.value;
  }
}

Domain Services

Cross-cutting business logic that involves multiple entities belongs in domain services.

// Pricing domain service
export class PricingDomainService {
  // Calculate discount based on membership and quantity
  calculateDiscount(
    user: User,
    itemCount: number
  ): number {
    let discountRate = 0;
 
    // Premium members get 10% discount
    if (user.isPremiumMember()) {
      discountRate = 0.1;
    }
 
    // Bulk purchases earn additional 5% discount
    if (itemCount >= 10) {
      discountRate += 0.05;
    }
 
    return discountRate;
  }
 
  // Calculate tax
  calculateTax(
    price: Money,
    taxRate: number
  ): Money {
    const taxAmount = price.getAmount() * taxRate;
    return new Money(taxAmount, price.getCurrency());
  }
 
  // Calculate final price (discount and tax applied)
  calculateFinalPrice(
    basePrice: Money,
    user: User,
    itemCount: number,
    taxRate: number
  ): Money {
    const discountRate = this.calculateDiscount(user, itemCount);
    const discountedPrice = basePrice.multiply(1 - discountRate);
    const tax = this.calculateTax(discountedPrice, taxRate);
    return discountedPrice.add(tax);
  }
}
 
// Inventory domain service
export class InventoryDomainService {
  // Check if stock is sufficient
  hasEnoughStock(product: Product, requestedQuantity: number): boolean {
    return product.getStock() >= requestedQuantity;
  }
 
  // Reserve stock
  reserveStock(product: Product, quantity: number): void {
    if (!this.hasEnoughStock(product, quantity)) {
      throw new Error('Insufficient stock');
    }
    product.reserveStock(quantity);
  }
 
  // Release reserved stock
  releaseStock(product: Product, quantity: number): void {
    product.releaseStock(quantity);
  }
}

Repository Interfaces

Repositories define contracts in the domain layer, with implementations in the infrastructure layer.

// Repository interfaces (Domain Layer)
export interface IUserRepository {
  save(user: User): Promise<void>;
  findById(userId: UserId): Promise<User | null>;
  findByEmail(email: Email): Promise<User | null>;
  delete(userId: UserId): Promise<void>;
  findAll(): Promise<User[]>;
}
 
export interface IOrderRepository {
  save(order: Order): Promise<void>;
  findById(orderId: OrderId): Promise<Order | null>;
  findByUserId(userId: UserId): Promise<Order[]>;
  delete(orderId: OrderId): Promise<void>;
}
 
export interface IProductRepository {
  save(product: Product): Promise<void>;
  findById(productId: ProductId): Promise<Product | null>;
  findAll(): Promise<Product[]>;
  delete(productId: ProductId): Promise<void>;
}

Application Layer Implementation

Application Services and Use Cases

Use cases model what users want to achieve. They orchestrate domain logic and coordinate external dependencies.

// DTOs: Data Transfer Objects
export class CreateOrderRequest {
  constructor(
    readonly userId: string,
    readonly productIds: string[],
    readonly quantities: number[]
  ) {}
}
 
export class CreateOrderResponse {
  constructor(
    readonly orderId: string,
    readonly totalPrice: string,
    readonly status: string
  ) {}
}
 
// Use Case: Create an order
export class CreateOrderUseCase {
  constructor(
    private userRepository: IUserRepository,
    private productRepository: IProductRepository,
    private orderRepository: IOrderRepository,
    private pricingService: PricingDomainService,
    private inventoryService: InventoryDomainService,
    private paymentGateway: IPaymentGateway,
    private notificationService: INotificationService
  ) {}
 
  async execute(request: CreateOrderRequest): Promise<CreateOrderResponse> {
    // Fetch user
    const user = await this.userRepository.findById(
      new UserId(request.userId)
    );
    if (!user) {
      throw new Error('User not found');
    }
 
    // Fetch products and verify stock
    const products: Product[] = [];
    for (const productId of request.productIds) {
      const product = await this.productRepository.findById(
        new ProductId(productId)
      );
      if (!product) {
        throw new Error(`Product ${productId} not found`);
      }
      products.push(product);
    }
 
    // Reserve stock
    for (let i = 0; i < products.length; i++) {
      this.inventoryService.reserveStock(
        products[i],
        request.quantities[i]
      );
    }
 
    // Create order entity
    const order = Order.create(
      user,
      products,
      request.quantities,
      this.pricingService
    );
 
    // Process payment
    const paymentResult = await this.paymentGateway.charge(
      user.getEmail(),
      order.getTotalPrice()
    );
 
    if (!paymentResult.success) {
      // Release stock on payment failure
      for (let i = 0; i < products.length; i++) {
        this.inventoryService.releaseStock(
          products[i],
          request.quantities[i]
        );
      }
      throw new Error('Payment failed');
    }
 
    // Persist order
    order.markAsPaid(paymentResult.transactionId);
    await this.orderRepository.save(order);
 
    // Send confirmation
    await this.notificationService.sendOrderConfirmation(
      user.getEmail(),
      order
    );
 
    return new CreateOrderResponse(
      order.getId().getValue(),
      order.getTotalPrice().toString(),
      order.getStatus()
    );
  }
}
 
// Application Service
export class OrderApplicationService {
  constructor(
    private createOrderUseCase: CreateOrderUseCase,
    // Other use cases...
  ) {}
 
  async createOrder(request: CreateOrderRequest): Promise<CreateOrderResponse> {
    return this.createOrderUseCase.execute(request);
  }
}

Infrastructure Layer

Repository Implementation

Prisma-based implementation of domain repository interfaces:

// Implementing domain interface
export class UserRepository implements IUserRepository {
  constructor(private prisma: PrismaClient) {}
 
  async save(user: User): Promise<void> {
    await this.prisma.user.upsert({
      where: { id: user.getId().getValue() },
      update: {
        email: user.getEmail().getValue(),
        name: user.getName(),
        age: user.getAge(),
        updatedAt: user.getUpdatedAt(),
      },
      create: {
        id: user.getId().getValue(),
        email: user.getEmail().getValue(),
        name: user.getName(),
        age: user.getAge(),
        createdAt: user.getCreatedAt(),
        updatedAt: user.getUpdatedAt(),
      },
    });
  }
 
  async findById(userId: UserId): Promise<User | null> {
    const record = await this.prisma.user.findUnique({
      where: { id: userId.getValue() },
    });
 
    if (!record) {
      return null;
    }
 
    return this.toDomain(record);
  }
 
  async findByEmail(email: Email): Promise<User | null> {
    const record = await this.prisma.user.findUnique({
      where: { email: email.getValue() },
    });
 
    if (!record) {
      return null;
    }
 
    return this.toDomain(record);
  }
 
  async findAll(): Promise<User[]> {
    const records = await this.prisma.user.findMany();
    return records.map((record) => this.toDomain(record));
  }
 
  async delete(userId: UserId): Promise<void> {
    await this.prisma.user.delete({
      where: { id: userId.getValue() },
    });
  }
 
  // Convert database record to domain entity
  private toDomain(record: any): User {
    return new User(
      new UserId(record.id),
      new Email(record.email),
      record.name,
      record.age,
      record.createdAt,
      record.updatedAt
    );
  }
}

External Services (Port Implementations)

Payment gateways and other integrations implement port interfaces:

// Port interface (Application Layer)
export interface IPaymentGateway {
  charge(email: string, amount: Money): Promise<PaymentResult>;
  refund(transactionId: string): Promise<RefundResult>;
}
 
export interface PaymentResult {
  success: boolean;
  transactionId?: string;
  errorMessage?: string;
}
 
// Implementation (Infrastructure Layer)
export class StripePaymentGateway implements IPaymentGateway {
  constructor(private stripeClient: Stripe) {}
 
  async charge(email: string, amount: Money): Promise<PaymentResult> {
    try {
      const paymentIntent = await this.stripeClient.paymentIntents.create({
        amount: Math.round(amount.getAmount() * 100), // cents
        currency: amount.getCurrency().toLowerCase(),
        receipt_email: email,
      });
 
      if (paymentIntent.status === 'succeeded') {
        return {
          success: true,
          transactionId: paymentIntent.id,
        };
      } else {
        return {
          success: false,
          errorMessage: `Payment status: ${paymentIntent.status}`,
        };
      }
    } catch (error) {
      return {
        success: false,
        errorMessage: (error as Error).message,
      };
    }
  }
 
  async refund(transactionId: string): Promise<RefundResult> {
    try {
      const refund = await this.stripeClient.refunds.create({
        payment_intent: transactionId,
      });
 
      return {
        success: refund.status === 'succeeded',
        refundId: refund.id,
      };
    } catch (error) {
      return {
        success: false,
        errorMessage: (error as Error).message,
      };
    }
  }
}

Dependency Injection and DI Containers

DI Container Design

Centralize all dependency management:

// DI Container
export class Container {
  private instances: Map<string, any> = new Map();
  private factories: Map<string, () => any> = new Map();
 
  // Register singleton
  registerSingleton<T>(key: string, factory: () => T): void {
    this.factories.set(key, factory);
  }
 
  // Register instance directly
  registerInstance<T>(key: string, instance: T): void {
    this.instances.set(key, instance);
  }
 
  // Get instance (cached)
  get<T>(key: string): T {
    if (this.instances.has(key)) {
      return this.instances.get(key);
    }
 
    const factory = this.factories.get(key);
    if (!factory) {
      throw new Error(`No factory registered for key: ${key}`);
    }
 
    const instance = factory();
    this.instances.set(key, instance);
    return instance;
  }
}
 
// Setup function
export function setupDependencies(): Container {
  const container = new Container();
  const prisma = new PrismaClient();
 
  // Infrastructure Layer
  container.registerInstance('prisma', prisma);
  container.registerInstance(
    'stripeClient',
    new Stripe(process.env.STRIPE_SECRET_KEY || '')
  );
 
  // Repositories
  container.registerSingleton('userRepository', () => {
    return new UserRepository(container.get('prisma'));
  });
  container.registerSingleton('productRepository', () => {
    return new ProductRepository(container.get('prisma'));
  });
  container.registerSingleton('orderRepository', () => {
    return new OrderRepository(container.get('prisma'));
  });
 
  // Domain Services
  container.registerSingleton('pricingDomainService', () => {
    return new PricingDomainService();
  });
  container.registerSingleton('inventoryDomainService', () => {
    return new InventoryDomainService();
  });
 
  // External Services
  container.registerSingleton('paymentGateway', () => {
    return new StripePaymentGateway(container.get('stripeClient'));
  });
  container.registerSingleton('emailService', () => {
    return new SendgridEmailService(process.env.SENDGRID_API_KEY || '');
  });
  container.registerSingleton('notificationService', () => {
    return new NotificationService(container.get('emailService'));
  });
 
  // Use Cases
  container.registerSingleton('createOrderUseCase', () => {
    return new CreateOrderUseCase(
      container.get('userRepository'),
      container.get('productRepository'),
      container.get('orderRepository'),
      container.get('pricingDomainService'),
      container.get('inventoryDomainService'),
      container.get('paymentGateway'),
      container.get('notificationService')
    );
  });
 
  // Application Services
  container.registerSingleton('orderApplicationService', () => {
    return new OrderApplicationService(
      container.get('createOrderUseCase')
    );
  });
 
  return container;
}

Architecture Analysis with Antigravity Agents

Automated Architecture Violation Detection

Have Antigravity's agents analyze your codebase for Clean Architecture violations:

Instruct Antigravity:

Analyze the project's dependency structure and report:

1. Reverse Dependencies:
   - Domain layer depending on Application or Infrastructure layers
   - Use Case layer directly depending on Adapter layer

2. Layer Skipping:
   - Presentation layer directly depending on Domain layer
   - Infrastructure and Domain layers directly communicating

3. Mixed Concerns:
   - Business logic mingled with HTTP handlers
   - Database schema structure leaking into domain models

4. Port-Adapter Violations:
   - External libraries depending on business logic
   - Repository interfaces depending on implementations

Antigravity generates a dependency graph and returns results like:

【ARCHITECTURE VIOLATION REPORT】

【Severity: HIGH】
- OrderController (Presentation) → Order (Domain) direct dependency
  ↳ Cause: new Order() instantiation in controller
  ↳ Fix: Route through CreateOrderUseCase

- User (Domain) → UserRepository implementation (Infrastructure)
  ↳ Cause: importing infrastructure package in domain/entities/User.ts
  ↳ Fix: Import interface only

【Severity: MEDIUM】
- OrderApplicationService → Stripe (external library) direct dependency
  ↳ Cause: StripePaymentGateway in application layer
  ↳ Fix: Move to infrastructure layer

Automated Refactoring Suggestions

Antigravity can propose fixes:

Instruct Antigravity:

Fix the violation in src/presentation/controllers/OrderController.ts:

// Current code (violates rule)
export class OrderController {
  post(req: Request, res: Response) {
    const order = new Order(req.body);  // ← direct instantiation (violation)
    // ...
  }
}

Refactoring points:
1. Inject CreateOrderUseCase via DI container
2. Create order through use case
3. Show refactored code

Refactored code:

export class OrderController {
  constructor(private orderApplicationService: OrderApplicationService) {}
 
  async post(req: Request, res: Response) {
    try {
      const request = new CreateOrderRequest(
        req.body.userId,
        req.body.productIds,
        req.body.quantities
      );
      const response = await this.orderApplicationService.createOrder(request);
      res.json(response);
    } catch (error) {
      res.status(400).json({ error: (error as Error).message });
    }
  }
}

Defining Rules with AGENTS.md

Define project-specific architecture rules that Antigravity enforces:

# Architecture Rules for Clean Architecture + DDD
 
## Layer Dependencies
- domain/* must not depend on application/*, infrastructure/*, presentation/*
- application/* must not depend on infrastructure/* (interfaces only)
- presentation/* must not directly depend on infrastructure/* (via application layer)
 
## Module Imports
- No imports from domain/ to infrastructure/
- No imports from domain/ to external/
- Business logic (methods) must be in the same file as value objects
 
## Repository Pattern
- Repository implementations only in infrastructure/repositories/
- Application layer depends only on IRepository interfaces
- Repositories manage entity lifecycles
 
## Dependency Injection
- All external dependencies via constructor injection
- No global variables or Service Locator pattern
- Concentrate container setup in config/

Antigravity references these rules for violation detection and refactoring suggestions.


Conclusion

Implementing Clean Architecture × DDD with Antigravity provides significant benefits:

1. Maintainability

  • Layer separation limits change impact
  • Domain logic directly reflects business requirements

2. Testability

  • Test business logic without databases or external services
  • Faster test execution

3. Reusability

  • Same business logic works across Web, CLI, API interfaces

4. Scalability

  • Supports larger teams and simpler onboarding
  • Architecture remains clear as project grows

5. Antigravity Automation

  • Auto-detect architecture violations
  • Propose refactorings and auto-fix issues
  • Maintain design quality continuously

For production applications requiring long-term maintenance and team collaboration, Clean Architecture and DDD represent sound architectural decisions. Antigravity's agent capabilities help you maintain these principles efficiently as you scale.


Reference

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-06-28
When Streaming Works Locally but Arrives All at Once in Production — Field Notes on Proxy Buffering and Silent Stalls
Stream Gemini through Antigravity over SSE and it flows token-by-token on localhost, then freezes for seconds and dumps the whole answer in production. Field notes on measuring the stall first, then killing proxy buffering, idle disconnects, and reconnect-driven double generation.
App Dev2026-05-03
Building Idempotency Keys and Dedupe Stores in TypeScript with Antigravity
A production guide to designing idempotency keys and dedupe stores in TypeScript with Antigravity — covering Stripe webhook retries, Temporal replays, and the Cloudflare KV / Redis / Postgres trade-offs you actually need to choose between.
App Dev2026-05-01
Zero-Downtime Database Migrations with Antigravity: The Expand-Contract Pattern in Production
A complete production guide to running breaking schema changes—type swaps, column renames, table splits—with zero user-facing downtime, using the Expand-Contract pattern with Antigravity's AI assistance.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →