Core Architecture
Reference guide to the core architecture of the React Native app, covering patterns, component interactions, and technology stack.
Core Architecture
Overview
This document outlines the core architecture for our React Native mobile application. It defines the fundamental patterns, structures, and frameworks that support all features and functionality. Our architecture employs a hybrid approach combining feature-based UI organization with domain-based business logic for optimal scalability and maintainability.
Purpose & Scope
This document provides a comprehensive reference for understanding the overall application architecture. It's intended for all developers working on the project, particularly those who need to understand how different system components interact and the distinction between shared utilities and domain-specific code.
Core Architecture Components
1. Architectural Overview
Our application follows a hybrid architecture that combines:
- Feature-based organization for UI/UX concerns
- Domain-based organization for business logic and data
- Clear separation between shared utilities and domain-specific code
2. Technology Stack
| Category | Technology | Purpose |
|---|---|---|
| Framework | React Native | Cross-platform mobile application development |
| Language | TypeScript | Static typing and improved developer productivity |
| Server State | TanStack Query | Data fetching, caching, synchronization, and server state management |
| Client State | Zustand | Global application state management |
| Navigation | React Navigation | Screen routing and navigation management |
| HTTP Client | Axios | Promise-based HTTP requests, integrated with TanStack Query |
| Styling | React Native StyleSheet / Unistyles | Native styling with performance optimization and theme support |
| Testing | Jest & React Testing Library | Unit and integration testing |
| E2E Testing | Detox | End-to-end testing for mobile applications |
3. Hybrid Architecture: Features + Domains
Our architecture employs a dual approach for optimal organization:
Domain-Based Business Logic
- Purpose: Organize business logic by domain entities
- Location:
core/domains/[domain]/ - Contents: API services, types, hooks, query keys
- Examples: Products, Users, Auth, Orders
Feature-Based UI Organization
- Purpose: Organize user-facing functionality by features
- Location:
features/[feature]/ - Contents: Screens, components, navigation
- Examples: Shopping Cart, Product Catalog, User Profile
Shared Utilities
- Purpose: Cross-cutting concerns used everywhere
- Location:
core/shared/ - Contents: Generic hooks, utilities, types
- Examples: useDebounce, formatDate, ApiResponse
4. Core Infrastructure
The core infrastructure is divided into domain-specific and shared components:
5. Shared vs Domain Guidelines
Clear separation between shared and domain-specific code is crucial:
What Goes in Shared Folders
- core/shared/hooks/: Generic React hooks (useDebounce, useLocalStorage)
- core/shared/utils/: General utilities (formatDate, capitalizeString)
- core/shared/types/: Cross-domain types (ApiResponse, AsyncState, ErrorResponse)
What Stays in Domain Folders
- core/domains/[domain]/hooks.ts: Domain queries (useProducts, useUser)
- core/domains/[domain]/types.ts: Domain models (Product, User)
- core/domains/[domain]/api.ts: Domain services
6. Feature Module Structure
Each feature module is a self-contained unit focused on user-facing functionality:
7. State Management Architecture
Our application clearly separates different types of state:
- Server State: Managed by TanStack Query through domain hooks
- Client State: Managed by Zustand for global application state
- Component State: Local state for UI-specific concerns
8. Navigation System
The navigation system provides type-safe routing:
- Navigation Container: Central React Navigation setup
- Feature-Based Navigation: Each feature defines its screens
- Deep Linking: Support for external app opening
- Type Safety: Full TypeScript integration
9. Communication Patterns
Features communicate through several patterns:
- Domain Services: Shared data access through domain hooks
- Event Bus: Publish-subscribe for loose coupling
- Shared State: Query cache and global state
- Navigation: Parameter passing between screens
10. Authentication Architecture
Authentication is handled as a special domain with additional concerns:
Key features:
- Automatic token refresh through interceptors
- Session management with event-driven updates
- Secure token storage abstraction
Design Principles
1. Feature-Based Modularity
We organize UI/UX by features rather than technical concerns to create clear user journey mapping and better team ownership.
(Do ✅) Group related UI components by feature
- This approach makes it easier to understand the complete user journey within a feature.
- Example: All components related to product browsing are grouped in
features/product-catalog/.
(Consider 🤔) When a feature becomes too large, consider breaking it into sub-features
- Large features (>15-20 components) can become difficult to maintain.
- Use a subdirectory structure to organize related components within large features.
2. Domain-Driven Design
We organize business logic by domains for better scalability and maintainability. This separation has proven to significantly reduce complexity as applications grow.
(Do ✅) Identify clear domain boundaries based on business entities
- Group related business logic, types, and API calls by domain entity.
- Example: Everything related to products is in
core/domains/products/.
(Don't ❌) Mix domain concerns within a single domain module
- Avoid including user-related functionality in the product domain.
- Each domain should have a single, well-defined responsibility.
3. Clear Separation of Concerns
Our architecture maintains clear boundaries between different responsibilities:
(Do ✅) Use appropriate tools for different state needs
- Server State vs. Client State: TanStack Query for server data, Zustand for client-only state
- Shared vs. Domain: Place truly shared utilities in
core/shared, domain-specific code in domains - UI vs. Business Logic: Features handle UI/UX, domains handle data and business rules
4. Type Safety
Comprehensive TypeScript throughout the application improves reliability and developer experience:
(Do ✅) Define complete type interfaces for all data structures
- API Types: Full request/response typing with proper documentation
- State Types: Typed stores and hooks for compile-time safety
- Navigation Types: Type-safe routes and parameters
(Don't ❌) Use any types or ignore TypeScript warnings
- Type safety is only effective when consistently applied throughout the codebase.
- If you find yourself using
any, it might indicate a design issue that needs addressing.
5. Performance First
We prioritize performance considerations in our architecture design:
(Do ✅) Implement performance optimizations from the start
- Optimized Rendering: Strategic memoization of components and callbacks
- Query Optimization: Smart caching strategies for data fetching
- Code Splitting: Domain-based lazy loading for faster initial load
(Consider 🤔) Adding performance monitoring to identify bottlenecks
- Implement basic performance tracking to identify areas for improvement.
- Regular performance audits help maintain a fast application experience.
6. Developer Experience
Our architecture is designed to enhance developer productivity:
(Do ✅) Maintain consistent patterns across the codebase
- Consistent Patterns: Predictable organization makes onboarding easier
- Clear Boundaries: Obvious where code belongs reduces decision fatigue
- Self-Documenting: Structure reveals intent and guides development
Implementation Considerations
1. Adding New Domains
Domains represent business entities and contain related business logic. When creating a new domain, follow these steps:
Create domain folder
Create a new directory in core/domains/ with a descriptive name representing the business entity (e.g., products, orders, payments).
Add optional files as needed
Consider adding these files for more complex domains:
queryKeys.ts- For consistent query key managementevents.ts- For pub/sub communicationstore.ts- For domain-specific UI stateutils.ts- For domain-specific utility functions
(Do ✅) Follow the established domain structure consistently
- A consistent structure makes the codebase more predictable and easier to navigate.
- Each domain should have the same basic file organization.
(Don't ❌) Mix domain logic with feature-specific UI code
- Domains should contain only business logic, data fetching, and types.
- Feature-specific UI components should stay in feature folders.
2. Creating New Features
Features represent user-facing functionality and organize related UI components. To create a new feature:
Create feature folder
Add a new directory in features/ with a name describing the user-facing functionality (e.g., product-catalog, shopping-cart, checkout).
Add feature-specific state if needed
For UI state that's specific to the feature but doesn't belong in a domain:
(Do ✅) Keep features focused on user-facing functionality
- Features should primarily contain UI components and layout logic.
- The API for a feature should be clean and limited to what's necessary for other parts of the app.
(Be Aware ❗) Features can get complex - consider sub-features
- For very large features, consider creating sub-feature folders.
- Use the same organizational patterns within sub-features.
3. Shared vs Domain Decision
Deciding where code belongs is critical for maintaining the architecture. Use this decision tree:
(Do ✅) Ask critical questions when deciding placement
- "Is this specific to one business domain?" → Domain folder
- "Is it a reusable utility used across domains?" → Shared folder
- "Is it a UI component specific to one feature?" → Feature folder
(Don't ❌) Create ambiguous or overlapping responsibilities
- Avoid creating utilities that do the same thing in different places.
- Don't add business logic to UI components.
4. Cross-Feature Communication
Features should never directly import from other features. Instead, use these patterns:
(Do ✅) Use domain hooks for shared data access
(Do ✅) Use events for loose coupling
(Do ✅) Pass parameters through navigation
(Don't ❌) Import directly between features
(Consider 🤔) Using Signals for real-time updates
- For real-time updates that need to flow across features, consider using a signals library or reactive state.
- This provides a performance benefit for frequently changing values.
Related Documents
Design Philosophy & Tradeoffs
Honest evaluation of UTA's architectural patterns, design decisions, and guidance for teams considering adoption of our Feature-Domain Hybrid approach.
Project Structure
Comprehensive reference for the project's directory structure, file organization, and module boundaries with clear shared/domain separation.