Fix Blossom CORS headers and add root-level upload routes (v0.36.12)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add proper CORS headers for Blossom endpoints including X-SHA-256, X-Content-Length, X-Content-Type headers required by blossom-client-sdk - Add root-level Blossom routes (/upload, /media, /mirror, /report, /list/) for clients like Jumble that expect Blossom at root - Export BaseURLKey from pkg/blossom for use by app handlers - Make blossomRootHandler return URLs with /blossom prefix so blob downloads work via the registered /blossom/ route - Remove Access-Control-Allow-Credentials header (not needed for * origin) - Add Access-Control-Expose-Headers for X-Reason and other response headers Files modified: - app/blossom.go: Add blossomRootHandler, use exported BaseURLKey - app/server.go: Add CORS handling for blossom paths, register root routes - pkg/blossom/server.go: Fix CORS headers, export BaseURLKey - pkg/blossom/utils.go: Minor formatting - pkg/version/version: Bump to v0.36.12 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
# Strategic DDD Patterns
|
||||
|
||||
Strategic DDD patterns address the large-scale structure of a system: how to divide it into bounded contexts, how those contexts relate, and how to prioritize investment across subdomains.
|
||||
|
||||
## Bounded Context
|
||||
|
||||
### Definition
|
||||
|
||||
A Bounded Context is an explicit boundary within which a domain model exists. Inside the boundary, all terms have specific, unambiguous meanings. The same term may mean different things in different bounded contexts.
|
||||
|
||||
### Why It Matters
|
||||
|
||||
- **Linguistic clarity** - "Customer" in Sales means something different than "Customer" in Shipping
|
||||
- **Model isolation** - Changes to one model don't cascade across the system
|
||||
- **Team autonomy** - Teams can work independently within their context
|
||||
- **Focused complexity** - Each context solves one set of problems well
|
||||
|
||||
### Identification Heuristics
|
||||
|
||||
1. **Language divergence** - When stakeholders use the same word differently, there's a context boundary
|
||||
2. **Department boundaries** - Organizational structure often mirrors domain structure
|
||||
3. **Process boundaries** - End-to-end business processes often define context edges
|
||||
4. **Data ownership** - Who is the authoritative source for this data?
|
||||
5. **Change frequency** - Parts that change together should stay together
|
||||
|
||||
### Example: E-Commerce Platform
|
||||
|
||||
| Context | "Order" means... | "Product" means... |
|
||||
|---------|------------------|-------------------|
|
||||
| **Catalog** | N/A | Displayable item with description, images, categories |
|
||||
| **Inventory** | N/A | Stock keeping unit with quantity and location |
|
||||
| **Sales** | Shopping cart ready for checkout | Line item with price |
|
||||
| **Fulfillment** | Shipment to be picked and packed | Physical item to ship |
|
||||
| **Billing** | Invoice to collect payment | Taxable good |
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
#### Separate Deployables
|
||||
Each bounded context as its own service/application.
|
||||
|
||||
```
|
||||
catalog-service/
|
||||
├── src/domain/Product.ts
|
||||
└── src/infrastructure/CatalogRepository.ts
|
||||
|
||||
sales-service/
|
||||
├── src/domain/Product.ts # Different model!
|
||||
└── src/domain/Order.ts
|
||||
```
|
||||
|
||||
#### Module Boundaries
|
||||
Bounded contexts as modules within a monolith.
|
||||
|
||||
```
|
||||
src/
|
||||
├── catalog/
|
||||
│ └── domain/Product.ts
|
||||
├── sales/
|
||||
│ └── domain/Product.ts # Different model!
|
||||
└── shared/
|
||||
└── kernel/Money.ts # Shared kernel
|
||||
```
|
||||
|
||||
## Context Map
|
||||
|
||||
### Definition
|
||||
|
||||
A Context Map is a visual and documented representation of how bounded contexts relate to each other. It makes integration patterns explicit.
|
||||
|
||||
### Integration Patterns
|
||||
|
||||
#### Partnership
|
||||
|
||||
Two contexts develop together with mutual dependencies. Changes are coordinated.
|
||||
|
||||
```
|
||||
┌─────────────┐ Partnership ┌─────────────┐
|
||||
│ Catalog │◄──────────────────►│ Inventory │
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
**Use when**: Two teams must succeed or fail together.
|
||||
|
||||
#### Shared Kernel
|
||||
|
||||
A small, shared model that multiple contexts depend on. Changes require agreement from all consumers.
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ Sales │ │ Billing │
|
||||
└──────┬──────┘ └──────┬──────┘
|
||||
│ │
|
||||
└─────────► Money ◄──────────────┘
|
||||
(shared kernel)
|
||||
```
|
||||
|
||||
**Use when**: Core concepts genuinely need the same model.
|
||||
**Danger**: Creates coupling. Keep shared kernels minimal.
|
||||
|
||||
#### Customer-Supplier
|
||||
|
||||
Upstream context (supplier) provides data/services; downstream context (customer) consumes. Supplier considers customer needs.
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ Catalog │───── supplies ────►│ Sales │
|
||||
│ (upstream) │ │ (downstream)│
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
**Use when**: One context clearly serves another, and the supplier is responsive.
|
||||
|
||||
#### Conformist
|
||||
|
||||
Downstream adopts upstream's model without negotiation. Upstream doesn't accommodate downstream needs.
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ External │───── dictates ────►│ Our App │
|
||||
│ API │ │ (conformist)│
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
**Use when**: Upstream won't change (third-party API), and their model is acceptable.
|
||||
|
||||
#### Anti-Corruption Layer (ACL)
|
||||
|
||||
Translation layer that protects a context from external models. Transforms data at the boundary.
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌───────┐ ┌─────────────┐
|
||||
│ Legacy │───────►│ ACL │───────►│ New System │
|
||||
│ System │ └───────┘ └─────────────┘
|
||||
```
|
||||
|
||||
**Use when**: Upstream model would pollute downstream; translation is worth the cost.
|
||||
|
||||
```typescript
|
||||
// Anti-Corruption Layer example
|
||||
class LegacyOrderAdapter {
|
||||
constructor(private legacyApi: LegacyOrderApi) {}
|
||||
|
||||
translateOrder(legacyOrder: LegacyOrder): Order {
|
||||
return new Order({
|
||||
id: OrderId.from(legacyOrder.order_num),
|
||||
customer: this.translateCustomer(legacyOrder.cust_data),
|
||||
items: legacyOrder.line_items.map(this.translateLineItem),
|
||||
// Transform legacy status codes to domain concepts
|
||||
status: this.mapStatus(legacyOrder.stat_cd),
|
||||
});
|
||||
}
|
||||
|
||||
private mapStatus(legacyCode: string): OrderStatus {
|
||||
const mapping: Record<string, OrderStatus> = {
|
||||
'OP': OrderStatus.Open,
|
||||
'SH': OrderStatus.Shipped,
|
||||
'CL': OrderStatus.Closed,
|
||||
};
|
||||
return mapping[legacyCode] ?? OrderStatus.Unknown;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Open Host Service
|
||||
|
||||
A context provides a well-defined protocol/API for others to consume.
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
┌──────────►│ Reports │
|
||||
│ └─────────────┘
|
||||
┌───────┴───────┐ ┌─────────────┐
|
||||
│ Catalog API │──►│ Search │
|
||||
│ (open host) │ └─────────────┘
|
||||
└───────┬───────┘ ┌─────────────┐
|
||||
└──────────►│ Partner │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
**Use when**: Multiple downstream contexts need access; worth investing in a stable API.
|
||||
|
||||
#### Published Language
|
||||
|
||||
A shared language format (schema) for communication between contexts. Often combined with Open Host Service.
|
||||
|
||||
Examples: JSON schemas, Protocol Buffers, GraphQL schemas, industry standards (HL7 for healthcare).
|
||||
|
||||
#### Separate Ways
|
||||
|
||||
Contexts have no integration. Each solves its needs independently.
|
||||
|
||||
**Use when**: Integration cost exceeds benefit; duplication is acceptable.
|
||||
|
||||
### Context Map Notation
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ CONTEXT MAP │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────┐ Partnership ┌─────────┐ │
|
||||
│ │ Sales │◄────────────────────────────►│Inventory│ │
|
||||
│ │ (U,D) │ │ (U,D) │ │
|
||||
│ └────┬────┘ └────┬────┘ │
|
||||
│ │ │ │
|
||||
│ │ Customer/Supplier │ │
|
||||
│ ▼ │ │
|
||||
│ ┌─────────┐ │ │
|
||||
│ │ Billing │◄──────────────────────────────────┘ │
|
||||
│ │ (D) │ Conformist │
|
||||
│ └─────────┘ │
|
||||
│ │
|
||||
│ Legend: U = Upstream, D = Downstream │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Subdomain Classification
|
||||
|
||||
### Core Domain
|
||||
|
||||
The essential differentiator. This is where competitive advantage lives.
|
||||
|
||||
**Characteristics**:
|
||||
- Unique to this business
|
||||
- Complex, requires deep expertise
|
||||
- Frequently changing as business evolves
|
||||
- Worth significant investment
|
||||
|
||||
**Strategy**: Build in-house with best talent. Invest heavily in modeling.
|
||||
|
||||
### Supporting Subdomain
|
||||
|
||||
Necessary for the business but not a differentiator.
|
||||
|
||||
**Characteristics**:
|
||||
- Important but not unique
|
||||
- Moderate complexity
|
||||
- Changes less frequently
|
||||
- Custom implementation needed
|
||||
|
||||
**Strategy**: Build with adequate (not exceptional) investment. May outsource.
|
||||
|
||||
### Generic Subdomain
|
||||
|
||||
Solved problems with off-the-shelf solutions.
|
||||
|
||||
**Characteristics**:
|
||||
- Common across industries
|
||||
- Well-understood solutions exist
|
||||
- Rarely changes
|
||||
- Not a differentiator
|
||||
|
||||
**Strategy**: Buy or use open-source. Don't reinvent.
|
||||
|
||||
### Example: E-Commerce Platform
|
||||
|
||||
| Subdomain | Type | Strategy |
|
||||
|-----------|------|----------|
|
||||
| Product Recommendation Engine | Core | In-house, top talent |
|
||||
| Inventory Management | Supporting | Build, adequate investment |
|
||||
| Payment Processing | Generic | Third-party (Stripe, etc.) |
|
||||
| User Authentication | Generic | Third-party or standard library |
|
||||
| Shipping Logistics | Supporting | Build or integrate vendor |
|
||||
| Customer Analytics | Core | In-house, strategic investment |
|
||||
|
||||
## Ubiquitous Language
|
||||
|
||||
### Definition
|
||||
|
||||
A common language shared by developers and domain experts. It appears in conversations, documentation, and code.
|
||||
|
||||
### Building Ubiquitous Language
|
||||
|
||||
1. **Listen to experts** - Use their terminology, not technical jargon
|
||||
2. **Challenge vague terms** - "Process the order" → What exactly happens?
|
||||
3. **Document glossary** - Maintain a living dictionary
|
||||
4. **Enforce in code** - Class and method names use the language
|
||||
5. **Refine continuously** - Language evolves with understanding
|
||||
|
||||
### Language in Code
|
||||
|
||||
```typescript
|
||||
// Bad: Technical terms
|
||||
class OrderProcessor {
|
||||
handleOrderCreation(data: OrderData): void {
|
||||
this.validateData(data);
|
||||
this.persistToDatabase(data);
|
||||
this.sendNotification(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Good: Ubiquitous language
|
||||
class OrderTaker {
|
||||
placeOrder(cart: ShoppingCart): PlacedOrder {
|
||||
const order = cart.checkout();
|
||||
order.confirmWith(this.paymentGateway);
|
||||
this.orderRepository.save(order);
|
||||
this.domainEvents.publish(new OrderPlaced(order));
|
||||
return order;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Glossary Example
|
||||
|
||||
| Term | Definition | Context |
|
||||
|------|------------|---------|
|
||||
| **Order** | A confirmed purchase with payment collected | Sales |
|
||||
| **Shipment** | Physical package(s) sent to fulfill an order | Fulfillment |
|
||||
| **SKU** | Stock Keeping Unit; unique identifier for inventory | Inventory |
|
||||
| **Cart** | Uncommitted collection of items a customer intends to buy | Sales |
|
||||
| **Listing** | Product displayed for purchase in the catalog | Catalog |
|
||||
|
||||
### Anti-Pattern: Technical Language Leakage
|
||||
|
||||
```typescript
|
||||
// Bad: Database terminology leaks into domain
|
||||
order.setForeignKeyCustomerId(customerId);
|
||||
order.persist();
|
||||
|
||||
// Bad: HTTP concerns leak into domain
|
||||
order.deserializeFromJson(request.body);
|
||||
order.setHttpStatus(200);
|
||||
|
||||
// Good: Domain language only
|
||||
order.placeFor(customer);
|
||||
orderRepository.save(order);
|
||||
```
|
||||
|
||||
## Strategic Design Decisions
|
||||
|
||||
### When to Split a Bounded Context
|
||||
|
||||
Split when:
|
||||
- Different parts need to evolve at different speeds
|
||||
- Different teams need ownership
|
||||
- Model complexity is becoming unmanageable
|
||||
- Language conflicts are emerging within the context
|
||||
|
||||
Don't split when:
|
||||
- Transaction boundaries would become awkward
|
||||
- Integration cost outweighs isolation benefit
|
||||
- Single team can handle the complexity
|
||||
|
||||
### When to Merge Bounded Contexts
|
||||
|
||||
Merge when:
|
||||
- Integration overhead is excessive
|
||||
- Same team owns both
|
||||
- Models are converging naturally
|
||||
- Separate contexts create artificial complexity
|
||||
|
||||
### Dealing with Legacy Systems
|
||||
|
||||
1. **Bubble context** - New bounded context with ACL to legacy
|
||||
2. **Strangler fig** - Gradually replace legacy feature by feature
|
||||
3. **Conformist** - Accept legacy model if acceptable
|
||||
4. **Separate ways** - Rebuild independently, migrate data later
|
||||
Reference in New Issue
Block a user