UTA DevHub

Domain Integration

Connecting business components with domain logic and services

Domain Integration

Overview

Domain integration is a critical aspect of our UI architecture, focusing on how business components connect with domain logic and services. This guide covers patterns and best practices for maintaining clean separation of concerns while enabling effective communication between UI and domain layers.

Design Principles

Separation of Concerns

  • UI components handle presentation
  • Domain services handle business logic
  • Clear boundaries between layers

Why it matters: This separation ensures that UI components remain reusable across different features, while domain logic can evolve independently. It enables specialized testing strategies for each layer and makes maintenance significantly easier when requirements change.

Integration Patterns

1. Domain Hooks

The most common pattern for connecting UI to domain logic:

// core/domains/products/hooks/useProduct.ts
export function useProduct(productId: string) {
  const [product, setProduct] = useState<Product | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
 
  useEffect(() => {
    // Domain logic for fetching product
  }, [productId]);
 
  return { product, loading, error };
}
 
// ui/business/ProductCard/ProductCard.tsx
export function ProductCard({ productId }: { productId: string }) {
  const { product, loading, error } = useProduct(productId);
  
  if (loading) return <Skeleton />;
  if (error) return <ErrorDisplay error={error} />;
  
  return (
    <Card>
      <Text>{product.name}</Text>
      <PriceTag amount={product.price} />
    </Card>
  );
}

2. Domain Services

For more complex business logic:

// core/domains/cart/services/cartService.ts
export class CartService {
  async addItem(productId: string, quantity: number): Promise<void> {
    // Domain logic for adding items
  }
  
  async removeItem(productId: string): Promise<void> {
    // Domain logic for removing items
  }
}
 
// ui/business/AddToCartButton/AddToCartButton.tsx
export function AddToCartButton({ productId }: { productId: string }) {
  const cartService = useCartService();
  
  const handlePress = async () => {
    try {
      await cartService.addItem(productId, 1);
    } catch (error) {
      // Error handling
    }
  };
  
  return <Button onPress={handlePress}>Add to Cart</Button>;
}

3. Domain Events

For cross-cutting concerns:

// core/domains/events/analytics.ts
export const analytics = {
  trackEvent: (event: AnalyticsEvent) => {
    // Event tracking logic
  }
};
 
// ui/business/ProductCard/ProductCard.tsx
export function ProductCard({ productId }: { productId: string }) {
  const handlePress = () => {
    analytics.trackEvent({
      type: 'product_view',
      productId
    });
  };
  
  return <Card onPress={handlePress}>...</Card>;
}

Best Practices

Do's ✅

  • (Do ✅) Use domain hooks for data fetching and state management
  • (Do ✅) Keep UI components focused on presentation
  • (Do ✅) Handle loading and error states consistently
  • (Do ✅) Use shared types between UI and domains
  • (Do ✅) Implement proper error boundaries

Don'ts ❌

  • (Don't ❌) Put business logic in UI components
  • (Don't ❌) Duplicate domain logic in UI layer
  • (Don't ❌) Skip error handling
  • (Don't ❌) Mix UI and domain concerns
  • (Don't ❌) Create tight coupling between layers

Common Challenges

1. Loading States

// Simple loading state
function ProductCard({ productId }: { productId: string }) {
  const { product, loading } = useProduct(productId);
  
  if (loading) return <Skeleton />;
  return <Card>{product.name}</Card>;
}

2. Error Handling

// Simple error handling
function ProductCard({ productId }: { productId: string }) {
  const { product, error } = useProduct(productId);
  
  if (error) return <ErrorDisplay error={error} />;
  return <Card>{product.name}</Card>;
}

Testing Strategies

1. Unit Testing

// ui/business/ProductCard/__tests__/ProductCard.test.tsx
describe('ProductCard', () => {
  it('displays product name', () => {
    const { getByText } = render(
      <ProductCard productId="123" />
    );
    
    expect(getByText('Test Product')).toBeTruthy();
  });
  
  it('handles loading state', () => {
    const { getByTestId } = render(
      <ProductCard productId="123" />
    );
    
    expect(getByTestId('skeleton')).toBeTruthy();
  });
});

2. Integration Testing

// ui/business/ProductCard/__tests__/ProductCard.integration.test.tsx
describe('ProductCard Integration', () => {
  it('fetches and displays product data', async () => {
    const { getByText } = render(
      <ProductCard productId="123" />
    );
    
    await waitFor(() => {
      expect(getByText('Test Product')).toBeTruthy();
    });
  });
});

On this page