Loading learning content...
You've gathered requirements, estimated scale, and analyzed tradeoffs. Now comes the pivotal moment in system design: translating abstract requirements into concrete architectural components. This is where design becomes tangible—where 'the system should support millions of users' transforms into specific services, databases, queues, and caches that collectively deliver that capability.
Component identification is simultaneously the most creative and the most disciplined phase of system design. It requires imagination to envision how pieces might fit together, and rigor to ensure those pieces are well-defined, appropriately scoped, and cohesively integrated.
This page establishes the foundational skill of decomposing systems into building blocks. You'll learn systematic approaches for identifying components, techniques for defining component boundaries, and patterns that guide decomposition across different system types.
By the end of this page, you will understand how to systematically identify architectural components from requirements, define clear component boundaries and responsibilities, apply decomposition patterns across different domains, and recognize common components that appear in most distributed systems.
Before diving into identification techniques, we need precision about what we mean by component in system design. This term is used loosely across software engineering, so let's establish clear definitions.
In the context of high-level design, a component is an autonomous unit of functionality with:
Components are abstract—they can be implemented as microservices, serverless functions, monolith modules, background workers, or even third-party services. At the high-level design stage, we care about what functions they serve, not how they're implemented.
| Component Type | Description | Examples |
|---|---|---|
| Compute Components | Execute business logic and handle requests | API servers, background workers, stream processors |
| Data Components | Store and serve persistent data | Databases, caches, blob stores, search indices |
| Messaging Components | Enable asynchronous communication between services | Message queues, event buses, pub/sub systems |
| Gateway Components | Manage external access and routing | API gateways, load balancers, CDNs |
| Infrastructure Components | Support observability, security, and operations | Logging services, monitoring, secret managers |
A common misconception is that every component must become a separate microservice. In reality, multiple components might live in a single deployment (modular monolith), or a single logical component might span multiple processes (sharded database). Component identification is about logical decomposition—implementation decisions come later.
Component identification seems straightforward in theory but presents substantial challenges in practice. The core tension lies between two opposing forces:
Under-decomposition (too few components):
Over-decomposition (too many components):
The goal is to find the appropriate granularity that balances autonomy, cohesion, and operational complexity. This is deeply context-dependent—there's no universal 'right' number of components.
The worst outcome is achieving the complexity of microservices without their benefits. If your 'independent' services require coordinated deployments, share databases, and have chatty communication, you've built a distributed monolith. It's slower than a monolith and harder to operate than well-designed microservices. Thoughtful component identification helps avoid this trap.
Several proven strategies guide component identification. These aren't mutually exclusive—production systems typically employ a combination based on context.
Align components with business domains using Bounded Contexts from Domain-Driven Design (DDD). Each bounded context represents a coherent area of the business with its own ubiquitous language, models, and rules.
Example: E-commerce platform decomposed by domain:
Each context owns its data and logic, communicating with others through well-defined interfaces. This mirrors organizational structure and enables Conway's Law to work in your favor.
Organize components around technical capabilities that can be reused across domains. This approach extracts cross-cutting concerns into shared infrastructure.
Example: Platform capabilities:
Capability-based components become internal platforms that other components consume, enabling consistency and reducing duplication.
Group functionality around primary user journeys or use cases. This is particularly effective for systems with distinct user flows that share limited data.
Example: Video platform use cases:
Each flow can evolve independently based on its specific optimization goals and user needs.
Partition components based on data ownership and access patterns. Components are organized around the data they own, with clear rules for data access.
Example: Social network by data ownership:
This strategy naturally prevents shared databases and makes data ownership explicit, crucial for maintaining consistency in distributed systems.
| Strategy | Best For | Watch Out For |
|---|---|---|
| Domain-Driven | Complex business domains with clear boundaries | Domains that overlap or have ambiguous ownership |
| Capability-Based | Cross-cutting technical concerns, platform teams | Over-extracting and creating too many shared services |
| Use Case-Based | Distinct user journeys with different optimization needs | Shared data and logic across use cases requiring coordination |
| Data-Oriented | Systems where data ownership is critical (GDPR, compliance) | Business logic that spans multiple data entities |
Component identification is iterative, not linear. However, a structured approach increases the likelihood of a coherent design. Here's a systematic process that works across most system types:
Start with your functional requirements and use cases. Identify:
Nouns often become data components or domain services. Verbs often become operations within components or separate processing services.
Example: 'Users can upload videos which are then transcoded and distributed to viewers'
Group related nouns and verbs into coherent domains. Look for:
Draw tentative boundaries around clusters. These become candidate components.
For each candidate component, articulate its purpose in one sentence. If you can't, the component is probably too broad.
✅ Good: 'The Notification Service handles delivering messages to users across email, SMS, and push channels'
❌ Poor: 'The Core Service handles user management, order processing, and notification delivery'
Components with multiple responsibilities should be split.
For each candidate component, define:
Ideally, each piece of data has exactly one owner. If multiple components need write access to the same data, reconsider boundaries.
Determine how components will communicate:
Heavy synchronous coupling suggests components might belong together. Natural async boundaries suggest good separation points.
Test your decomposition against system requirements:
Component identification is rarely correct on the first pass. As you diagram the system and trace data flows, you'll discover:
Iterate until the design feels cohesive and each component has a clear, singular purpose.
When in doubt, start with fewer, larger components. It's easier to split a well-designed component later than to merge poorly conceived microservices. Many successful systems start as modular monoliths, extracting services only when the need becomes clear.
Certain components appear repeatedly across different system designs. Recognizing these patterns accelerates identification and leverages proven solutions.
A single entry point for client requests that handles:
Appears in: Nearly every client-facing system at scale
Dedicated component for identity management:
Appears in: Any system with user accounts
Centralized notification delivery across channels:
Appears in: E-commerce, social platforms, SaaS applications
Asynchronous processing for binary content:
Appears in: Video platforms, image-heavy apps, document management
Dedicated search infrastructure:
Appears in: E-commerce, content platforms, enterprise search
Coordinator for multi-step processes:
Appears in: Order fulfillment, approval workflows, data pipelines
Performance optimization through caching:
Appears in: High-traffic read-heavy systems
Asynchronous communication backbone:
Appears in: Event-driven architectures, decoupled microservices
Let's apply component identification to a realistic system: a food delivery platform like DoorDash or Uber Eats.
Functional Requirements Summary:
Key Nouns: Customers, Restaurants, Drivers, Orders, Menus, Items, Payments, Reviews, Locations
Key Verbs: Browse, Search, Order, Accept, Reject, Dispatch, Track, Pay, Rate, Navigate
Grouping by coherent domains:
Customer Domain:
Restaurant Domain:
Driver Domain:
Order Domain:
Dispatch Domain:
Payment Domain:
Review Domain:
✅ Order Service: Manages order lifecycle from creation to completion ✅ Dispatch Service: Matches available drivers with orders based on location and optimization ✅ Payment Service: Handles all monetary transactions and settlements ✅ Location Service: Tracks and serves real-time driver positions ✅ Notification Service: Delivers status updates across channels
Analyzing the design reveals needs for:
This decomposition represents one valid approach. Real-world systems evolve: Location Service might merge with Dispatch Service if they're always deployed together, or Order Service might split into Cart Service and Fulfillment Service as complexity grows. Start with clear boundaries and adjust based on actual operational needs.
Component identification doesn't end with a list. Each component should be documented with enough detail for the design to be implemented and evaluated.
For each component, define:
1. Purpose Statement One sentence describing what this component does and why it exists.
2. Responsibilities
3. Data Ownership
4. Key Operations
5. Dependencies
6. Outputs
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
# Order Service Component Specification purpose: | Manages the complete lifecycle of customer orders from creation through fulfillment and completion. responsibilities: in_scope: - Order creation and cart management - Order status transitions and validation - Order history storage and retrieval - Coordinating order events to other services out_of_scope: - Payment processing (Payment Service) - Driver assignment (Dispatch Service) - Customer data management (Customer Service) data_ownership: entities: - Orders (primary entity) - OrderItems (line items within orders) - OrderStatusHistory (audit trail) volumes: - Expected: 100K orders/day initially - Growth: 10x over 2 years key_operations: - CreateOrder: Creates new order from cart latency: < 200ms p99 throughput: 500 req/sec peak - GetOrder: Retrieves order details latency: < 50ms p99 throughput: 2000 req/sec peak - UpdateOrderStatus: Transitions order state latency: < 100ms p99 throughput: 1000 req/sec peak dependencies: synchronous: - CustomerService: Validate customer, get address - RestaurantService: Validate menu items, prices - PricingService: Calculate fees, apply promotions asynchronous: - PaymentCompleted event from PaymentService - DeliveryCompleted event from DispatchService outputs: events_published: - OrderCreated - OrderConfirmed - OrderReadyForPickup - OrderDelivered - OrderCancelledIn interview settings, you won't write formal specifications. But articulating these details verbally demonstrates thoroughness. For real projects, this documentation prevents ambiguity and enables parallel implementation by different teams.
Even experienced architects make mistakes during component identification. Recognizing these patterns helps avoid them.
Mistake: Creating one service per database table (UserService, OrderService, AddressService, CartService, CartItemService...)
Problem: This fragments related functionality. A single operation requires orchestrating many services. Operational complexity explodes.
Solution: Group entities by domain boundary, not by table. Cart and CartItem belong together in Order context.
Mistake: Organizing by technology (Frontend, Backend, Database, Cache, Queue)
Problem: This doesn't reflect business domains. Changes to a single feature touch every 'component'. No independent evolution.
Solution: Each component should own its full stack from API to data storage. Technology is implementation, not architecture.
Mistake: Creating a separate service for every potential reuse opportunity (StringUtilsService, ValidationService, LoggingService)
Problem: Overhead of network calls, deployment, and monitoring for trivial functionality. Libraries work better for utilities.
Solution: Extract services for capabilities that require independence (different scaling, different data, different teams), not for code reuse.
Mistake: Defining components without considering data ownership and access patterns
Problem: Multiple services end up reading/writing the same database, creating hidden coupling and consistency issues.
Solution: Data ownership must be a primary consideration. If two components need the same data, reconsider boundaries.
Component identification transforms requirements into architectural building blocks. Let's consolidate the key principles:
What's next:
With components identified, our next step is visualizing their relationships through system architecture diagrams. The next page covers how to effectively communicate system structure through diagrams that serve as blueprints for implementation.
You now understand how to systematically identify architectural components from system requirements. This skill forms the foundation of high-level design, enabling you to translate abstract requirements into concrete, implementable building blocks.