Skip to content

Clean Architecture & SOLID Principles Analysis

Traditional React development often violates fundamental software engineering principles. TDI2 transforms React applications to comply with Clean Architecture and SOLID design principles, bringing enterprise-grade architectural standards to frontend development.

Clean Architecture defines strict dependency rules across layers:

  • Entities: Core business objects and rules
  • Use Cases: Application-specific business logic
  • Interface Adapters: Controllers, presenters, gateways
  • Frameworks & Drivers: UI, databases, external services

Typical React Component

function ProductPage({ productId }) {
// VIOLATION: Business logic mixed with UI
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
const [cartItems, setCartItems] = useState([]);
// VIOLATION: Direct API calls in component
useEffect(() => {
fetch(`/api/products/${productId}`)
.then(res => res.json())
.then(data => {
setProduct(data);
setLoading(false);
});
}, [productId]);
// VIOLATION: Business rules in UI layer
const addToCart = (product) => {
if (product.stock > 0 && !cartItems.find(item => item.id === product.id)) {
setCartItems([...cartItems, product]);
// Direct API call from UI
fetch('/api/cart', { method: 'POST', body: JSON.stringify(product) });
}
};
// 100+ lines of mixed concerns
}

Architectural Issues:

  • Business logic scattered throughout components
  • Direct API dependencies in UI layer
  • No clear separation between entities and UI
  • Testing requires mocking HTTP calls and DOM

Entity Layer (Core Business Objects)

// Pure business entities
export interface Product {
id: string;
name: string;
price: number;
stock: number;
}
export interface CartItem extends Product {
quantity: number;
}

Use Cases Layer (Business Logic Services)

@Service()
export class ProductService implements ProductServiceInterface {
constructor(
private productRepository: Inject<ProductRepositoryInterface>,
private cartService: Inject<CartServiceInterface>
) {}
// Pure business logic - no UI dependencies
async loadProduct(id: string): Promise<Product> {
const product = await this.productRepository.findById(id);
if (!product) {
throw new ProductNotFoundError(id);
}
return product;
}
canAddToCart(product: Product): boolean {
return product.stock > 0 && !this.cartService.hasItem(product.id);
}
}

Interface Adapter Layer (Repository Pattern)

@Service()
export class ProductRepository implements ProductRepositoryInterface {
async findById(id: string): Promise<Product | null> {
// HTTP adapter - easily swappable
const response = await fetch(`/api/products/${id}`);
return response.json();
}
}

Framework Layer (React Components)

// Pure UI presentation - no business logic
function ProductPage({ productService }: ServicesProps) {
const product = productService.state.currentProduct;
const loading = productService.state.loading;
return (
<div>
{loading ? <Spinner /> : <ProductDetails product={product} />}
<AddToCartButton
onClick={() => productService.addToCart(product)}
disabled={!productService.canAddToCart(product)}
/>
</div>
);
}

Traditional React Violation

// Component handling UI, business logic, API calls, state management
function ShoppingCart() {
// 200+ lines doing everything
}

TDI2 Compliance

// Each class has single responsibility
class CartService { /* Only cart business logic */ }
class CartRepository { /* Only data access */ }
class CartComponent { /* Only UI rendering */ }

TDI2 Extension Pattern

// Extend without modifying existing code
@Service()
export class PremiumCartService extends CartService {
// Add premium features without changing base service
applyPremiumDiscount(items: CartItem[]): CartItem[] {
return items.map(item => ({
...item,
price: item.price * 0.9 // 10% premium discount
}));
}
}

Interface-Based Substitution

// Any implementation can replace another
interface PaymentServiceInterface {
processPayment(amount: number): Promise<PaymentResult>;
}
@Service()
export class StripePaymentService implements PaymentServiceInterface {
async processPayment(amount: number): Promise<PaymentResult> {
// Stripe implementation
}
}
@Service()
export class PayPalPaymentService implements PaymentServiceInterface {
async processPayment(amount: number): Promise<PaymentResult> {
// PayPal implementation
}
}

Focused Interfaces

// Instead of one large interface
interface OrderServiceInterface {
calculateTotal(items: CartItem[]): number;
}
interface OrderNotificationInterface {
sendConfirmation(order: Order): Promise<void>;
}
interface OrderPersistenceInterface {
saveOrder(order: Order): Promise<void>;
}
// Services implement only what they need
@Service()
export class OrderCalculationService implements OrderServiceInterface {
calculateTotal(items: CartItem[]): number {
// Only calculation logic
}
}

High-Level Modules Independent of Low-Level Details

// High-level service depends on abstraction
@Service()
export class CheckoutService {
constructor(
private paymentService: Inject<PaymentServiceInterface>, // Abstraction
private orderRepository: Inject<OrderRepositoryInterface>, // Abstraction
private notificationService: Inject<NotificationServiceInterface> // Abstraction
) {}
// Business logic doesn't know about Stripe, MySQL, or Email APIs
async processCheckout(cartItems: CartItem[]): Promise<Order> {
const total = this.calculateTotal(cartItems);
const payment = await this.paymentService.processPayment(total);
const order = await this.orderRepository.save({ cartItems, payment });
await this.notificationService.sendConfirmation(order);
return order;
}
}
// Testing requires mocking fetch, DOM, and complex state
test('should add product to cart', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve(mockProduct) })
);
render(<ProductPage productId="123" />);
// Complex DOM testing with HTTP mocks
});
// Test pure business logic
test('should add product to cart', () => {
const cartService = new CartService();
const product = { id: '123', name: 'Test', price: 10, stock: 5 };
cartService.addItem(product);
expect(cartService.state.items).toContainEqual(product);
});
// Test UI separately
test('should render product details', () => {
const mockProductService = {
state: { currentProduct: mockProduct, loading: false }
};
render(<ProductPage productService={mockProductService} />);
expect(screen.getByText('Test Product')).toBeInTheDocument();
});
  • Separation of Concerns: Clear boundaries between UI, business logic, and data access
  • Testability: Unit test business logic independently of UI and external dependencies
  • Maintainability: Changes to business rules don’t require UI modifications
  • Reusability: Services can be used across multiple components and applications
  • Familiar Patterns: Backend developers recognize dependency injection and service patterns
  • Scalability: Architecture supports large team development with clear ownership boundaries
  • Flexibility: Easy to swap implementations for different environments or requirements
  • Clear Structure: Developers know where to put different types of code
  • Parallel Development: Teams can work on services and UI independently
  • Refactoring Safety: Interface-based design makes refactoring safer and more predictable

TDI2 brings decades of proven enterprise architecture principles to React development, transforming frontend applications from component-centric to service-oriented architectures that scale with team size and application complexity.