Java & React Knowledge Review

Technical Interview Preparation Guide
A comprehensive guide covering React, TypeScript, CI/CD, Microservices, Cloud Architecture, and more.
Table of Contents
- React Fundamentals
- ES6 JavaScript vs TypeScript
- Accessibility Best Practices
- CI Steps for Front-End
- RESTful API Characteristics
- Java BFF Benefits
- Scrum/Agile for Quality
- React Re-render Optimization
- Jest vs Selenium/E2E Testing
- GraphQL vs REST
- Microservice Design Characteristics
- Test Coverage and Mocking
- CI/CD Pipeline Standard
- AWS Deployment for React & Java
- Cloud-First Banking Architecture
- Resilience Patterns in Java BFF
- API Security Best Practices
- Distributed Transactions & Saga
- Quality Gates for Financial Systems
- Performance Balancing Strategy
1. React Fundamentals
Components
- Definition: Reusable, self-contained building blocks of a React UI
- Types:
- Functional Components: JavaScript functions that return JSX (preferred in modern React)
- Class Components: ES6 classes extending React.Component (legacy)
- Role: Encapsulate UI logic, structure, and behavior
Props (Properties)
- Definition: Read-only data passed from parent to child components
- Characteristics:
- Immutable within the receiving component
- Enable component reusability and customization
- Flow unidirectionally (top-down)
- Use Cases: Configuration, callbacks, data passing
State
- Definition: Mutable data managed within a component
- Characteristics:
- Changes trigger re-renders
- Local and encapsulated to the component
- Managed via useState hook (functional) or this.setState (class)
- Use Cases: User input, toggle states, fetched data
When to Split a Component
| Signal | Action |
|---|---|
| Component exceeds 200-300 lines | Extract logical sections |
| Multiple responsibilities | Single Responsibility Principle |
| Repeated JSX patterns | Create reusable component |
| Complex nested conditionals | Extract into smaller components |
| Different update frequencies | Separate to optimize renders |
| Shared logic across components | Create custom hooks |
// Before: Monolithic component
function Dashboard() {
return (
<div>
{/* Header logic */}
{/* Sidebar logic */}
{/* Main content logic */}
{/* Footer logic */}
</div>
);
}
// After: Split components
function Dashboard() {
return (
<div>
<Header />
<Sidebar />
<MainContent />
<Footer />
</div>
);
}
2. ES6 JavaScript vs TypeScript
| Feature | ES6 JavaScript | TypeScript |
|---|---|---|
| Type System | Dynamic typing | Static typing with type annotations |
| Compile-time Errors | None (runtime only) | Catches errors at compile time |
| IDE Support | Basic autocomplete | Rich IntelliSense, refactoring |
| Interfaces | Not supported | Full interface support |
| Generics | Not supported | Full generic support |
| Enums | Not native | Native enum support |
| Access Modifiers | Not supported | public, private, protected |
| Build Step | Optional (Babel) | Required (tsc compiler) |
| Learning Curve | Lower | Higher (but manageable) |
| Refactoring | Error-prone | Safe with type checking |
TypeScript Benefits in React
// Type-safe props
interface UserCardProps {
name: string;
age: number;
email?: string; // Optional
onUpdate: (id: string) => void;
}
const UserCard: React.FC<UserCardProps> = ({ name, age, email, onUpdate }) => {
// TypeScript ensures correct prop usage
return <div>{name}</div>;
};
// Type-safe state
const [user, setUser] = useState<User | null>(null);
3. Accessibility Best Practices
1. Semantic HTML
// ❌ Bad
<div onClick={handleClick}>Click me</div>
// ✅ Good
<button onClick={handleClick}>Click me</button>
2. ARIA Labels and Roles
// Provide context for screen readers
<button aria-label="Close dialog" onClick={onClose}>
<CloseIcon />
</button>
<nav role="navigation" aria-label="Main navigation">
{/* Navigation items */}
</nav>
// Live regions for dynamic content
<div aria-live="polite" aria-atomic="true">
{statusMessage}
</div>
3. Keyboard Navigation
// Ensure all interactive elements are focusable
<div
tabIndex={0}
onKeyDown={(e) => e.key === 'Enter' && handleAction()}
role="button"
>
Interactive Element
</div>
// Focus management in modals
useEffect(() => {
if (isOpen) {
modalRef.current?.focus();
}
}, [isOpen]);
Additional Best Practices
- Color Contrast: Minimum 4.5:1 ratio for normal text
- Alt Text: Meaningful descriptions for images
- Form Labels: Associate labels with inputs using htmlFor/id
- Skip Links: Allow users to skip repetitive navigation
- Focus Indicators: Visible focus states for all interactive elements
4. CI Steps for Front-End
Minimal CI Pipeline Steps
# .github/workflows/frontend-ci.yml
name: Frontend CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. Checkout & Setup
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# 2. Install Dependencies
- name: Install dependencies
run: npm ci
# 3. Linting
- name: Lint code
run: npm run lint
# 4. Type Checking (TypeScript)
- name: Type check
run: npm run type-check
# 5. Unit Tests
- name: Run tests
run: npm run test:ci
# 6. Build
- name: Build application
run: npm run build
# 7. (Optional) Bundle Analysis
- name: Analyze bundle
run: npm run analyze
Summary of Steps
- Dependency Installation: npm ci for reproducible builds
- Linting: ESLint for code quality
- Type Checking: TypeScript compilation check
- Unit/Component Tests: Jest + React Testing Library
- Build: Production build verification
- Optional: Bundle size analysis, security audit (npm audit)
5. RESTful API Characteristics
Core Principles
| Characteristic | Description |
|---|---|
| Stateless | Each request contains all needed information |
| Resource-Based | URLs represent resources, not actions |
| HTTP Methods | Proper use of GET, POST, PUT, PATCH, DELETE |
| Uniform Interface | Consistent URL patterns and response formats |
| HATEOAS | Hypermedia links for discoverability |
Best Practices
# Resource Naming (Nouns, not verbs)
GET /api/v1/users # List users
GET /api/v1/users/{id} # Get specific user
POST /api/v1/users # Create user
PUT /api/v1/users/{id} # Replace user
PATCH /api/v1/users/{id} # Partial update
DELETE /api/v1/users/{id} # Delete user
# Nested Resources
GET /api/v1/users/{id}/orders
Response Standards
- Consistent Structure: { data, meta, errors }
- HTTP Status Codes: 200, 201, 204, 400, 401, 403, 404, 500
- Pagination: ?page=1&limit=20
- Filtering: ?status=active&sort=-createdAt
- Versioning: /api/v1/ or via headers
6. Java BFF Benefits
Why and When Was BFF Introduced?
The BFF (Backend for Frontend) pattern was coined by Sam Newman (author of "Building Microservices") at SoundCloud around 2015. It emerged as a solution to problems that arose when microservices architectures became popular.
The Problem Before BFF
In traditional microservices architecture, frontends had to deal with multiple backend services directly:
┌─────────────────┐
│ Web Browser │
└────────┬────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ User Service │ │ Order Service │ │ Product Service│
└───────────────┘ └───────────────┘ └───────────────┘
Problems This Caused:
- Multiple API calls: Frontend needed 5-10 calls to render one page
- Over-fetching: APIs returned data designed for all clients, not specific needs
- Under-fetching: Missing data required additional calls
- Complex frontend logic: Data aggregation and transformation in browser
- Different client needs: Mobile vs Web vs Smart TV needed different data
- Security exposure: Internal service details leaked to clients
- Tight coupling: Frontend changes required backend changes
What is BFF (Backend for Frontend)?
A dedicated backend layer tailored to specific frontend needs.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Web Browser │ │ Mobile App │ │ Smart TV │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Web BFF │ │ Mobile BFF │ │ TV BFF │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ User Service │ │ Order Service │ │Product Service│
└───────────────┘ └───────────────┘ └───────────────┘
When to Introduce BFF
| Scenario | BFF Needed? |
|---|---|
| Single frontend, simple CRUD | ❌ No |
| Multiple clients (web, mobile, TV) with different needs | ✅ Yes |
| Need to aggregate data from many microservices | ✅ Yes |
| Frontend making 5+ API calls per page | ✅ Yes |
| Security concerns (hiding internal APIs) | ✅ Yes |
| Different teams own frontend and backend | ✅ Yes |
| Need caching/rate limiting per client type | ✅ Yes |
| Simple monolith with one client | ❌ No |
Real-World Example: SoundCloud
Before BFF:
Mobile App needs to show user profile page:
1. GET /users/123 → User details
2. GET /users/123/tracks → User's tracks
3. GET /users/123/followers → Follower count
4. GET /users/123/following → Following count
5. GET /tracks/456/waveform → Waveform data for each track
... 10+ API calls, lots of unused data
After BFF:
Mobile App:
1. GET /mobile/bff/user-profile/123
→ Returns exactly what mobile needs in ONE call
{
name, avatar, trackCount, followerCount,
recentTracks: [{ title, duration, waveformUrl }]
}
Main Benefits
| Benefit | Description |
|---|---|
| API Aggregation | Combine multiple backend calls into single response |
| Response Shaping | Transform data specifically for UI needs |
| Security Layer | Handle auth, hide internal services |
| Caching | Reduce load on downstream services |
| Protocol Translation | Convert gRPC/SOAP to REST/GraphQL |
| Rate Limiting | Protect backend from frontend traffic spikes |
| Resilience | Implement circuit breakers, retries |
Why Java for BFF?
| Reason | Explanation |
|---|---|
| Performance | JVM is highly optimized for concurrent requests |
| Type Safety | Strong typing catches errors at compile time |
| Ecosystem | Spring Boot, Resilience4j, mature libraries |
| Team Skills | Many enterprises have Java expertise |
| Observability | Excellent monitoring/tracing tools |
| Async Support | CompletableFuture, WebFlux for parallel calls |
BFF vs API Gateway
| Aspect | API Gateway | BFF |
|---|---|---|
| Purpose | Routing, auth, rate limiting | Data aggregation, transformation |
| Logic | Minimal business logic | Client-specific business logic |
| Ownership | Platform/Infra team | Frontend team |
| Per-client | Usually one gateway | One per client type |
| Examples | Kong, AWS API Gateway | Custom Spring Boot service |
Often used together:
Client → API Gateway → BFF → Microservices
(routing) (aggregation)
Example Use Case
@RestController
@RequestMapping("/bff/dashboard")
public class DashboardBffController {
@GetMapping
public DashboardResponse getDashboard(@AuthUser User user) {
// Aggregate multiple service calls in parallel
CompletableFuture<UserProfile> profile = userService.getProfile(user.getId());
CompletableFuture<List<Order>> orders = orderService.getRecent(user.getId());
CompletableFuture<Notifications> notifications = notificationService.get(user.getId());
// Wait for all and combine for frontend
CompletableFuture.allOf(profile, orders, notifications).join();
// Shape response specifically for frontend needs
return DashboardResponse.builder()
.profile(profile.join())
.recentOrders(orders.join().stream().limit(5).toList())
.unreadCount(notifications.join().getUnreadCount())
.build();
}
}
Key Insight
BFF moves complexity from the frontend (browser/mobile) to a server where it's easier to optimize, secure, and monitor. It acts as a translation layer between generic microservices and specific frontend needs.
7. Scrum/Agile for Quality
Quality-Focused Scrum Practices
Definition of Done (DoD)
✅ Code reviewed by at least one peer
✅ Unit tests written (>80% coverage)
✅ Integration tests passing
✅ No critical/high SonarQube issues
✅ Documentation updated
✅ Accessibility requirements met
✅ Performance benchmarks passed
Sprint Ceremonies for Quality
| Ceremony | Quality Focus |
|---|---|
| Sprint Planning | Include testing effort in estimates |
| Daily Standup | Surface blockers early |
| Sprint Review | Demo working software, get feedback |
| Retrospective | Identify quality improvement areas |
| Backlog Refinement | Clarify acceptance criteria |
Best Practices
- Test-Driven Development (TDD): Write tests before code
- Pair Programming: Real-time code review
- Continuous Integration: Automated quality gates
- Technical Debt Sprints: Allocate capacity for refactoring
- Bug Triage: Address critical bugs immediately
- Shift-Left Testing: Test early in the development cycle
8. React Re-render Optimization
How React Re-rendering Works
The React Rendering Process
State/Props Change → Render Phase → Reconciliation → Commit Phase → DOM Update
(Virtual DOM) (Diffing) (Actual DOM)
- Render Phase: React calls your component function to get the new Virtual DOM
- Reconciliation: React compares (diffs) the new Virtual DOM with the previous one
- Commit Phase: React updates only the changed parts in the actual DOM
What Triggers a Re-render?
| Trigger | Description | Example |
|---|---|---|
| State Change | Calling setState or state setter from useState | setCount(count + 1) |
| Props Change | Parent passes different props | |
| Parent Re-renders | When parent re-renders, all children re-render by default | Parent state changes |
| Context Change | When context value changes | <Context.Provider value={newValue}> |
| Force Update | Explicitly forcing update (rare) | forceUpdate() in class components |
function Parent() {
const [count, setCount] = useState(0);
// When count changes:
// 1. Parent re-renders
// 2. Child re-renders (even if its props didn't change!)
// 3. GrandChild re-renders
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child /> {/* Re-renders even though it has no props! */}
</div>
);
}
function Child() {
console.log('Child rendered'); // Logs on every parent state change
return <GrandChild />;
}
function GrandChild() {
console.log('GrandChild rendered'); // Also logs!
return <div>I'm a grandchild</div>;
}
Shallow Comparison Explained
React uses shallow comparison (also called shallow equality) to determine if props or state have changed.
What is Shallow Comparison?
// Shallow comparison checks:
// 1. For primitives: Are the values equal?
// 2. For objects/arrays: Are the REFERENCES equal? (not the contents!)
// === Primitives (Shallow comparison works as expected) ===
const a = 5;
const b = 5;
a === b; // true ✅ - same value
const str1 = 'hello';
const str2 = 'hello';
str1 === str2; // true ✅ - same value
// === Objects (Shallow comparison compares REFERENCES) ===
const obj1 = { name: 'John' };
const obj2 = { name: 'John' };
obj1 === obj2; // false ❌ - different references (different objects in memory)
const obj3 = obj1;
obj1 === obj3; // true ✅ - same reference (pointing to same object)
// === Arrays (Same behavior as objects) ===
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
arr1 === arr2; // false ❌ - different references
const arr3 = arr1;
arr1 === arr3; // true ✅ - same reference
Why This Matters in React
function Parent() {
const [count, setCount] = useState(0);
// ❌ Problem: New object created on EVERY render
const user = { name: 'John', age: 30 };
// ❌ Problem: New array created on EVERY render
const items = ['apple', 'banana'];
// ❌ Problem: New function created on EVERY render
const handleClick = () => console.log('clicked');
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
{/* Even with React.memo, Child re-renders because props are new objects! */}
<MemoizedChild user={user} items={items} onClick={handleClick} />
</div>
);
}
const MemoizedChild = React.memo(({ user, items, onClick }) => {
console.log('Child rendered'); // Still logs every time!
return <div>{user.name}</div>;
});
Shallow Comparison Visualization
Re-render #1: Re-render #2:
┌─────────────────┐ ┌─────────────────┐
│ user (ref: 0x1) │ │ user (ref: 0x2) │ ← NEW reference!
│ { name: 'John' }│ │ { name: 'John' }│ Same content, but
└─────────────────┘ └─────────────────┘ React sees it as different
Shallow comparison: 0x1 === 0x2 → false → RE-RENDER!
Common Causes of Unnecessary Re-renders
1. Inline Object/Array Props
// ❌ Bad: New object on every render
<UserCard style={{ color: 'red' }} />
<List items={[1, 2, 3]} />
// ✅ Good: Stable reference
const cardStyle = { color: 'red' };
const numbers = [1, 2, 3];
<UserCard style={cardStyle} />
<List items={numbers} />
// ✅ Better: Define outside component or use useMemo
const CARD_STYLE = { color: 'red' }; // Outside component
function Parent() {
const items = useMemo(() => [1, 2, 3], []);
return <List items={items} />;
}
2. Inline Function Props
// ❌ Bad: New function on every render
<Button onClick={() => handleClick(id)} />
// ✅ Good: Stable function reference with useCallback
const handleButtonClick = useCallback(() => {
handleClick(id);
}, [id]);
<Button onClick={handleButtonClick} />
3. State Too High in Component Tree
// ❌ Bad: Input state in parent causes Header to re-render
function App() {
const [searchTerm, setSearchTerm] = useState('');
return (
<>
<Header /> {/* Re-renders on every keystroke! */}
<SearchInput value={searchTerm} onChange={setSearchTerm} />
<Content />
</>
);
}
// ✅ Good: State colocated in the component that uses it
function App() {
return (
<>
<Header /> {/* Never re-renders due to search */}
<SearchInput /> {/* Manages its own state */}
<Content />
</>
);
}
function SearchInput() {
const [searchTerm, setSearchTerm] = useState('');
return <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />;
}
Optimization Solutions in Detail
Solution 1: React.memo - Memoize Components
React.memo wraps a component and prevents re-renders if props haven't changed (using shallow comparison).
// Without React.memo - re-renders whenever parent re-renders
function ExpensiveList({ items }) {
console.log('ExpensiveList rendered');
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// With React.memo - only re-renders if **items** reference changes
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
console.log('ExpensiveList rendered');
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
});
// Custom comparison function for deep comparison (use sparingly!)
const ExpensiveList = React.memo(
function ExpensiveList({ items }) {
return <ul>{/* ... */}</ul>;
},
(prevProps, nextProps) => {
// Return true if props are equal (skip re-render)
// Return false if props are different (re-render)
return prevProps.items.length === nextProps.items.length &&
prevProps.items.every((item, i) => item.id === nextProps.items[i].id);
}
);
Solution 2: useMemo - Memoize Expensive Calculations
useMemo caches the result of a calculation and only recalculates when dependencies change.
function ProductList({ products, filterTerm }) {
// ❌ Bad: Filters and sorts on EVERY render
const filteredProducts = products
.filter(p => p.name.includes(filterTerm))
.sort((a, b) => a.price - b.price);
// ✅ Good: Only recalculates when products or filterTerm changes
const filteredProducts = useMemo(() => {
console.log('Filtering and sorting...'); // Only logs when dependencies change
return products
.filter(p => p.name.includes(filterTerm))
.sort((a, b) => a.price - b.price);
}, [products, filterTerm]);
return <ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// useMemo for stable object references
function UserProfile({ userId }) {
const [refreshCount, setRefreshCount] = useState(0);
// ❌ Bad: New object on every render
const config = { theme: 'dark', userId };
// ✅ Good: Stable reference, only changes when userId changes
const config = useMemo(() => ({
theme: 'dark',
userId
}), [userId]);
return (
<>
<button onClick={() => setRefreshCount(c => c + 1)}>Refresh</button>
<MemoizedSettings config={config} /> {/* Won't re-render on refresh */}
</>
);
}
Solution 3: useCallback - Memoize Functions
useCallback returns a memoized version of a callback that only changes when dependencies change.
function TodoList({ todos }) {
const [filter, setFilter] = useState('all');
// ❌ Bad: New function on every render
const handleDelete = (id) => {
deleteTodo(id);
};
// ✅ Good: Stable function reference
const handleDelete = useCallback((id) => {
deleteTodo(id);
}, []); // Empty deps = function never changes
// ✅ With dependencies
const handleToggle = useCallback((id) => {
toggleTodo(id, filter);
}, [filter]); // Function updates when filter changes
return (
<ul>
{todos.map(todo => (
<MemoizedTodoItem
key={todo.id}
todo={todo}
onDelete={handleDelete} // Stable reference
onToggle={handleToggle}
/>
))}
</ul>
);
}
const MemoizedTodoItem = React.memo(function TodoItem({ todo, onDelete, onToggle }) {
console.log(`Rendering todo: ${todo.id}`);
return (
<li>
{todo.text}
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
});
Solution 4: State Colocation
Move state as close as possible to where it's used.
// ❌ Bad: Global state causes entire app to re-render
function App() {
const [modalOpen, setModalOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [selectedTab, setSelectedTab] = useState('home');
return (
<div>
<Header /> {/* Re-renders on searchTerm change */}
<Tabs selected={selectedTab} onChange={setSelectedTab} />
<SearchBar value={searchTerm} onChange={setSearchTerm} />
<Content />
{modalOpen && <Modal onClose={() => setModalOpen(false)} />}
</div>
);
}
// ✅ Good: State colocated to relevant components
function App() {
return (
<div>
<Header />
<TabsWithState /> {/* Manages its own selectedTab state */}
<SearchBarWithState /> {/* Manages its own searchTerm state */}
<Content />
<ModalTrigger /> {/* Manages its own modalOpen state */}
</div>
);
}
function SearchBarWithState() {
const [searchTerm, setSearchTerm] = useState('');
return <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />;
}
Solution 5: Virtualization for Long Lists
Render only visible items instead of the entire list.
import { FixedSizeList } from 'react-window';
// ❌ Bad: Renders all 10,000 items
function BadList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// ✅ Good: Only renders visible items (~10-20 at a time)
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={400} // Viewport height
itemCount={items.length}
itemSize={35} // Row height
width="100%"
>
{Row}
</FixedSizeList>
);
}
Debugging Re-renders
Using React DevTools Profiler
- Open React DevTools → Profiler tab
- Click "Record" and interact with your app
- Stop recording and analyze:
- Which components re-rendered
- Why they re-rendered
- How long each render took
Adding Console Logs
function MyComponent({ data }) {
console.log('MyComponent rendered', { data });
useEffect(() => {
console.log('data changed:', data);
}, [data]);
return <div>{/* ... */}</div>;
}
Using why-did-you-render Library
// Setup (development only)
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, { trackAllPureComponents: true });
}
// Mark component to track
MyComponent.whyDidYouRender = true;
Summary: When to Use Each Optimization
| Problem | Solution |
|---|---|
| Child re-renders when parent re-renders (but child props unchanged) | React.memo |
| Expensive calculation runs on every render | useMemo |
| Function prop causes child to re-render | useCallback |
| State change in one area affects unrelated components | State Colocation |
| Rendering thousands of list items | Virtualization (react-window) |
| Object/array prop always triggers re-render | useMemo to stabilize reference |
Golden Rule: Don't optimize prematurely. Use React DevTools Profiler to identify actual bottlenecks first!
Additional Real-World Examples
Example 1: E-commerce Product Card with Multiple Optimizations
// ❌ Before: Every parent re-render causes all ProductCards to re-render
function ProductGrid({ products, cart, onAddToCart }) {
const [sortBy, setSortBy] = useState('price');
// Problem 1: Sorting on every render
const sortedProducts = products.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
return a.name.localeCompare(b.name);
});
return (
<div>
<select value={sortBy} onChange={e => setSortBy(e.target.value)}>
<option value="price">Price</option>
<option value="name">Name</option>
</select>
{sortedProducts.map(product => (
<ProductCard
key={product.id}
product={product}
// Problem 2: New object every render
cartInfo={{
inCart: cart.includes(product.id),
quantity: cart.filter(id => id === product.id).length
}}
// Problem 3: New function every render
onAdd={() => onAddToCart(product.id)}
/>
))}
</div>
);
}
// ✅ After: Optimized with useMemo, useCallback, and React.memo
function ProductGrid({ products, cart, onAddToCart }) {
const [sortBy, setSortBy] = useState('price');
// Solution 1: Memoize sorted products
const sortedProducts = useMemo(() => {
console.log('Sorting products...');
return [...products].sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
return a.name.localeCompare(b.name);
});
}, [products, sortBy]);
// Solution 3: Memoize the add to cart handler
const handleAddToCart = useCallback((productId) => {
onAddToCart(productId);
}, [onAddToCart]);
return (
<div>
<select value={sortBy} onChange={e => setSortBy(e.target.value)}>
<option value="price">Price</option>
<option value="name">Name</option>
</select>
{sortedProducts.map(product => (
<MemoizedProductCard
key={product.id}
product={product}
// Solution 2: Pass primitives instead of objects when possible
inCart={cart.includes(product.id)}
quantity={cart.filter(id => id === product.id).length}
onAdd={handleAddToCart}
/>
))}
</div>
);
}
// Memoized ProductCard - only re-renders when its specific props change
const MemoizedProductCard = React.memo(function ProductCard({
product,
inCart,
quantity,
onAdd
}) {
console.log(`Rendering ProductCard: ${product.name}`);
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => onAdd(product.id)}>
{inCart ? `In Cart (${quantity})` : 'Add to Cart'}
</button>
</div>
);
});
Example 2: Form with Debounced Search and Isolated State
// ❌ Before: Every keystroke re-renders the entire form
function SearchPage() {
const [searchTerm, setSearchTerm] = useState('');
const [filters, setFilters] = useState({ category: 'all', inStock: true });
const [results, setResults] = useState([]);
// This runs on every keystroke!
useEffect(() => {
fetchResults(searchTerm, filters).then(setResults);
}, [searchTerm, filters]);
return (
<div>
<Header /> {/* Re-renders on every keystroke! */}
<Sidebar /> {/* Re-renders on every keystroke! */}
<input
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
<FilterPanel filters={filters} onChange={setFilters} />
<ResultsList results={results} />
</div>
);
}
// ✅ After: Isolated search input with debouncing
function SearchPage() {
const [filters, setFilters] = useState({ category: 'all', inStock: true });
const [results, setResults] = useState([]);
// Search term handled in isolated component
const handleSearch = useCallback((term) => {
fetchResults(term, filters).then(setResults);
}, [filters]);
return (
<div>
<Header /> {/* Never re-renders due to search */}
<Sidebar /> {/* Never re-renders due to search */}
<DebouncedSearchInput onSearch={handleSearch} />
<MemoizedFilterPanel filters={filters} onChange={setFilters} />
<MemoizedResultsList results={results} />
</div>
);
}
// Isolated search input - manages its own state
function DebouncedSearchInput({ onSearch }) {
const [inputValue, setInputValue] = useState('');
// Debounce the search callback
const debouncedSearch = useMemo(
() => debounce((term) => onSearch(term), 300),
[onSearch]
);
const handleChange = (e) => {
const value = e.target.value;
setInputValue(value);
debouncedSearch(value);
};
return (
<input
value={inputValue}
onChange={handleChange}
placeholder="Search..."
/>
);
}
const MemoizedFilterPanel = React.memo(FilterPanel);
const MemoizedResultsList = React.memo(ResultsList);
Example 3: Context Optimization to Prevent Mass Re-renders
// ❌ Before: Every context update re-renders ALL consumers
const AppContext = React.createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
// One big context - any change re-renders everything
const value = {
user, setUser,
theme, setTheme,
notifications, setNotifications
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
// Problem: Header only needs theme, but re-renders when notifications change!
function Header() {
const { theme } = useContext(AppContext);
console.log('Header rendered'); // Logs when ANY context value changes
return <header className={theme}>...</header>;
}
// ✅ After: Split contexts by update frequency
const UserContext = React.createContext();
const ThemeContext = React.createContext();
const NotificationContext = React.createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
// Memoize context values to prevent unnecessary re-renders
const userValue = useMemo(() => ({ user, setUser }), [user]);
const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
const notificationValue = useMemo(
() => ({ notifications, setNotifications }),
[notifications]
);
return (
<UserContext.Provider value={userValue}>
<ThemeContext.Provider value={themeValue}>
<NotificationContext.Provider value={notificationValue}>
{children}
</NotificationContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>
);
}
// Now Header only re-renders when theme changes
function Header() {
const { theme } = useContext(ThemeContext);
console.log('Header rendered'); // Only logs when theme changes
return <header className={theme}>...</header>;
}
Example 4: Table with Cell-Level Optimization
// ❌ Before: Editing one cell re-renders entire table
function DataTable({ data, onCellUpdate }) {
return (
<table>
<tbody>
{data.map(row => (
<tr key={row.id}>
{Object.entries(row).map(([key, value]) => (
<td key={key}>
<input
value={value}
// New function for every cell on every render!
onChange={e => onCellUpdate(row.id, key, e.target.value)}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
// ✅ After: Cell-level isolation with memoization
function DataTable({ data, onCellUpdate }) {
return (
<table>
<tbody>
{data.map(row => (
<MemoizedTableRow
key={row.id}
row={row}
onCellUpdate={onCellUpdate}
/>
))}
</tbody>
</table>
);
}
const MemoizedTableRow = React.memo(function TableRow({ row, onCellUpdate }) {
console.log(`Rendering row: ${row.id}`);
return (
<tr>
{Object.entries(row).map(([key, value]) => (
<MemoizedTableCell
key={key}
rowId={row.id}
cellKey={key}
value={value}
onUpdate={onCellUpdate}
/>
))}
</tr>
);
});
const MemoizedTableCell = React.memo(function TableCell({
rowId,
cellKey,
value,
onUpdate
}) {
console.log(`Rendering cell: ${rowId}-${cellKey}`);
// Local state for immediate feedback
const [localValue, setLocalValue] = useState(value);
// Sync with parent when prop changes
useEffect(() => {
setLocalValue(value);
}, [value]);
// Debounce updates to parent
const debouncedUpdate = useMemo(
() => debounce((val) => onUpdate(rowId, cellKey, val), 300),
[rowId, cellKey, onUpdate]
);
const handleChange = (e) => {
const newValue = e.target.value;
setLocalValue(newValue); // Immediate local update
debouncedUpdate(newValue); // Debounced parent update
};
return (
<td>
<input value={localValue} onChange={handleChange} />
</td>
);
});
Example 5: Expensive Component with Lazy Rendering
// ❌ Before: Heavy chart renders on every parent update
function Dashboard({ userData, chartData, settings }) {
return (
<div>
<UserStats data={userData} />
{/* This expensive chart re-renders whenever userData or settings change */}
<HeavyChart data={chartData} />
<SettingsPanel settings={settings} />
</div>
);
}
// ✅ After: Isolated expensive component
function Dashboard({ userData, chartData, settings }) {
return (
<div>
<UserStats data={userData} />
{/* Chart is memoized and only re-renders when chartData changes */}
<MemoizedHeavyChart data={chartData} />
<SettingsPanel settings={settings} />
</div>
);
}
const MemoizedHeavyChart = React.memo(function HeavyChart({ data }) {
console.log('HeavyChart rendering...'); // Only when data changes
// Expensive calculation
const processedData = useMemo(() => {
console.log('Processing chart data...');
return data.map(point => ({
...point,
normalized: point.value / Math.max(...data.map(d => d.value)),
movingAverage: calculateMovingAverage(data, point.index)
}));
}, [data]);
return (
<div className="chart">
{/* Render complex chart */}
</div>
);
});
Example 6: Optimizing Children Prop Pattern
// ❌ Before: Wrapper re-renders children on every state change
function ExpandableSection({ title, children }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div>
<button onClick={() => setIsExpanded(!isExpanded)}>
{title} {isExpanded ? '▼' : '▶'}
</button>
{isExpanded && (
<div className="content">
{children} {/* Children re-render when isExpanded changes */}
</div>
)}
</div>
);
}
// Usage - ExpensiveContent re-renders on every toggle!
<ExpandableSection title="Details">
<ExpensiveContent data={data} />
</ExpandableSection>
// ✅ After: Children prop is already memoized by React!
// The issue is usually in HOW you pass children
// Solution 1: Children as prop (already optimized by React)
// React memoizes children passed as JSX
function Parent() {
return (
<ExpandableSection title="Details">
<ExpensiveContent data={data} /> {/* This is stable! */}
</ExpandableSection>
);
}
// Solution 2: Render prop with useCallback
function ExpandableSection({ title, renderContent }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div>
<button onClick={() => setIsExpanded(!isExpanded)}>
{title} {isExpanded ? '▼' : '▶'}
</button>
{isExpanded && (
<div className="content">
{renderContent()}
</div>
)}
</div>
);
}
// Usage with stable render function
function Parent() {
const renderContent = useCallback(() => (
<ExpensiveContent data={data} />
), [data]);
return (
<ExpandableSection
title="Details"
renderContent={renderContent}
/>
);
}
Performance Checklist
Before optimizing, ask yourself:
- Is there actually a performance problem? (Measure first!)
- Did I check React DevTools Profiler?
- Can I colocate state closer to where it's used?
- Am I creating new objects/arrays/functions in render?
- Are my context values memoized?
- Do I need all this data at this level of the tree?
- Would splitting the component help?
- For lists: Am I using stable keys? Should I virtualize?
9. Jest vs Selenium/E2E Testing
Comparison Table
| Aspect | Jest (Unit/Component) | Selenium/Playwright (E2E) |
|---|---|---|
| Scope | Individual units/components | Full user journeys |
| Speed | Fast (ms per test) | Slow (seconds per test) |
| Environment | jsdom (simulated) | Real browser |
| Flakiness | Low | Higher |
| Cost | Low | High (infrastructure) |
| Feedback Loop | Immediate | Delayed |
| Coverage Goal | 70-80% of tests | 10-20% of tests |
When to Use Each
Jest + React Testing Library
- Unit testing pure functions
- Component behavior testing
- Hook testing
- Integration between components
- Fast CI feedback
// Component test example
test('submits form with valid data', async () => {
render(<LoginForm onSubmit={mockSubmit} />);
await userEvent.type(screen.getByLabelText('Email'), 'test@example.com');
await userEvent.type(screen.getByLabelText('Password'), 'password123');
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(mockSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
Selenium/Playwright/Cypress
- Critical user paths (login, checkout)
- Cross-browser compatibility
- Visual regression testing
- Full integration with real APIs
- Performance testing
// E2E test example (Playwright)
test('complete purchase flow', async ({ page }) => {
await page.goto('/products');
await page.click('[data-testid="product-1"]');
await page.click('button:has-text("Add to Cart")');
await page.click('[data-testid="checkout"]');
await page.fill('#card-number', '4242424242424242');
await page.click('button:has-text("Pay")');
await expect(page.locator('.confirmation')).toBeVisible();
});
10. GraphQL vs REST
When to Choose GraphQL
| Scenario | Recommendation |
|---|---|
| Multiple clients with different data needs | GraphQL |
| Deeply nested/related data | GraphQL |
| Rapid frontend iteration | GraphQL |
| Reducing over-fetching/under-fetching | GraphQL |
| Real-time subscriptions | GraphQL |
When to Choose REST
| Scenario | Recommendation |
|---|---|
| Simple CRUD operations | REST |
| File uploads/downloads | REST |
| Caching requirements (HTTP caching) | REST |
| Team familiarity | REST |
| Public APIs | REST |
Microservices Considerations
GraphQL Gateway Pattern:
┌─────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Client │────▶│ GraphQL Gateway │────▶│ Microservice A │
└─────────┘ │ (Federation) │────▶│ Microservice B │
└──────────────────┘────▶│ Microservice C │
└─────────────────┘
Benefits in Microservices:
- Schema stitching/federation for unified API
- Client-driven queries across services
- Strong typing with schema
Challenges:
- N+1 query problems
- Caching complexity
- Rate limiting complexity
11. Microservice Design Characteristics
Technical Characteristics
| Characteristic | Description |
|---|---|
| Single Responsibility | One business capability per service |
| Independently Deployable | Deploy without affecting other services |
| Loosely Coupled | Minimal dependencies on other services |
| Highly Cohesive | Related functionality grouped together |
| Owns Its Data | Dedicated database per service |
| API-First | Well-defined contracts (OpenAPI/AsyncAPI) |
| Observable | Logging, metrics, tracing built-in |
| Resilient | Handles failures gracefully |
| Stateless | No local session state |
| Containerized | Runs in Docker/Kubernetes |
Design Principles
┌─────────────────────────────────────────────────────────────┐
│ API Gateway / BFF │
└─────────────────────────────────────────────────────────────┘
│ │ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ User │ │ Order │ │ Payment │ │ Notif │
│ Service │ │ Service │ │ Service │ │ Service │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ User DB │ │ Order DB│ │ Pay DB │ │ Notif DB│
└─────────┘ └─────────┘ └─────────┘ └─────────┘
Key Practices
- Domain-Driven Design: Bounded contexts define service boundaries
- Event-Driven Communication: Async messaging for loose coupling
- Health Checks: Liveness and readiness probes
- Configuration Externalization: Environment-specific configs
- Graceful Shutdown: Handle in-flight requests
12. Test Coverage and Mocking
Coverage Strategy
Test Pyramid:
┌───────┐
│ E2E │ 10% - Critical paths
┌┴───────┴┐
│Integration│ 20% - Service boundaries
┌┴──────────┴┐
│ Unit │ 70% - Business logic
└─────────────┘
Unit Test Coverage Goals
| Code Type | Target Coverage |
|---|---|
| Business Logic | 90%+ |
| Utilities/Helpers | 90%+ |
| React Components | 80%+ |
| API Integration | 70%+ |
| Configuration | 50%+ |
Mocking Strategies
1. Function/Module Mocking (Jest)
// Mock entire module
jest.mock('./userService', () => ({
getUser: jest.fn().mockResolvedValue({ id: 1, name: 'Test' })
}));
// Mock specific implementation
const mockFetch = jest.spyOn(global, 'fetch').mockResolvedValue({
json: () => Promise.resolve({ data: 'mocked' })
});
2. API Mocking (MSW - Mock Service Worker)
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.json([{ id: 1, name: 'John' }]));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
3. Component Mocking
jest.mock('./HeavyComponent', () => {
return function MockedComponent(props) {
return <div data-testid="mocked-heavy">{props.title}</div>;
};
});
Coverage Reporting
# Generate coverage report
npm test -- --coverage --coverageReporters=text --coverageReporters=lcov
# Enforce thresholds
# package.json
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
13. CI/CD Pipeline Standard
Complete Pipeline Overview
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Commit │───▶│ Build │───▶│ Test │───▶│ Deploy │───▶│ Monitor │
│ │ │ │ │ │ │ Staging │ │ │
└─────────┘ └─────────┘ └─────────┘ └────┬────┘ └─────────┘
│
┌────▼────┐
│ Manual │
│ Approval│
└────┬────┘
│
┌────▼────┐
│ Prod │
└─────────┘
Detailed Pipeline Stages
# Frontend Pipeline
stages:
- name: Build
steps:
- checkout
- npm ci
- npm run lint
- npm run type-check
- npm run build
- upload-artifact: dist/
- name: Test
steps:
- npm run test:unit -- --coverage
- npm run test:integration
- upload-coverage: coverage/
- sonarqube-scan
- name: Security
steps:
- npm audit --audit-level=high
- snyk test
- OWASP dependency check
- name: Deploy Staging
steps:
- download-artifact: dist/
- deploy-to-s3: staging-bucket
- invalidate-cloudfront
- run-e2e-tests
- name: Deploy Production
needs: manual-approval
steps:
- deploy-to-s3: prod-bucket
- invalidate-cloudfront
- smoke-tests
- notify-team
# Backend Pipeline
stages:
- name: Build
steps:
- checkout
- mvn clean compile
- mvn package -DskipTests
- docker build -t app:$VERSION .
- name: Test
steps:
- mvn test
- mvn verify (integration tests)
- jacoco-coverage-report
- sonarqube-scan
- name: Security
steps:
- OWASP dependency-check
- trivy scan (container)
- secrets scan
- name: Deploy
steps:
- push-to-ecr
- update-ecs-task
- health-check
- rollback-on-failure
14. AWS Deployment for React & Java
React Frontend Deployment
┌─────────────────────────────────────────────────────────────┐
│ CloudFront (CDN) │
│ - SSL/TLS termination │
│ - Edge caching │
│ - Custom domain │
└──────────────────────────┬──────────────────────────────────┘
│
┌───────────▼───────────┐
│ S3 Bucket │
│ - Static hosting │
│ - SPA config │
│ - Versioned builds │
└───────────────────────┘
Architecture Components
Frontend:
- S3: Static file hosting
- CloudFront: CDN, caching, HTTPS
- Route 53: DNS management
- Certificate Manager: SSL certificates
- WAF: Web Application Firewall
Backend:
- ECS Fargate: Container orchestration (or EKS)
- ALB: Load balancing, health checks
- ECR: Container registry
- RDS/Aurora: Database
- ElastiCache: Redis for caching
- Secrets Manager: Credentials
Java Backend Deployment
┌─────────────────────────────────────────────────────────────┐
│ Application Load Balancer │
└──────────────────────────┬──────────────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ ECS │ │ ECS │ │ ECS │
│ Task 1 │ │ Task 2 │ │ Task 3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────────┼────────────────┘
│
┌───────────▼───────────┐
│ Aurora PostgreSQL │
│ (Multi-AZ) │
└───────────────────────┘
Terraform Example
# S3 + CloudFront for React
resource "aws_s3_bucket" "frontend" {
bucket = "my-app-frontend"
}
resource "aws_cloudfront_distribution" "frontend" {
origin {
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
origin_id = "S3Origin"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
# ... additional config
}
# ECS for Java Backend
resource "aws_ecs_service" "backend" {
name = "backend-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.backend.arn
desired_count = 3
launch_type = "FARGATE"
load_balancer {
target_group_arn = aws_lb_target_group.backend.arn
container_name = "backend"
container_port = 8080
}
}
15. Cloud-First Banking Architecture
AWS Services Selection
| Layer | Service | Purpose |
|---|---|---|
| CDN | CloudFront | Static content, edge caching |
| API Gateway | API Gateway | Rate limiting, authentication |
| Compute | EKS / ECS Fargate | Container orchestration |
| Database | Aurora PostgreSQL | ACID transactions, Multi-AZ |
| Cache | ElastiCache Redis | Session, frequently accessed data |
| Queue | SQS / EventBridge | Async processing |
| Storage | S3 | Documents, backups |
| Security | WAF, Shield, KMS | Protection, encryption |
Auto-Scaling Configuration
# ECS Service Auto Scaling
AutoScaling:
min_capacity: 3
max_capacity: 50
policies:
- type: TargetTracking
metric: ECSServiceAverageCPUUtilization
target: 70
- type: TargetTracking
metric: ECSServiceAverageMemoryUtilization
target: 75
- type: StepScaling
metric: ALBRequestCountPerTarget
thresholds:
- lower: 1000
adjustment: +2
- lower: 5000
adjustment: +5
# Aurora Auto Scaling (Read Replicas)
AuroraAutoScaling:
min_replicas: 2
max_replicas: 15
target_cpu: 70
Observability Stack
┌─────────────────────────────────────────────────────────────┐
│ Observability Platform │
├──────────────────┬──────────────────┬───────────────────────┤
│ Logging │ Metrics │ Tracing │
├──────────────────┼──────────────────┼───────────────────────┤
│ CloudWatch Logs │ CloudWatch │ X-Ray / OpenTelemetry │
│ + Log Insights │ + Custom Metrics │ │
├──────────────────┼──────────────────┼───────────────────────┤
│ OpenSearch │ Prometheus │ Jaeger │
│ (ELK alternative)│ + Grafana │ │
└──────────────────┴──────────────────┴───────────────────────┘
Alerting: CloudWatch Alarms → SNS → PagerDuty/Slack
Dashboards: Grafana / CloudWatch Dashboards
APM: X-Ray Service Map
Key Banking Requirements
- Multi-AZ Deployment: High availability
- Encryption: At rest (KMS) and in transit (TLS 1.3)
- Audit Logging: CloudTrail for compliance
- DR Strategy: Cross-region replication, RTO < 4hrs, RPO < 1hr
- Network Isolation: VPC, private subnets, security groups
16. Resilience Patterns in Java BFF
Implementation with Resilience4j
1. Circuit Breaker
@Service
public class PaymentService {
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackPayment")
public PaymentResponse processPayment(PaymentRequest request) {
return paymentClient.process(request);
}
private PaymentResponse fallbackPayment(PaymentRequest request, Exception e) {
log.warn("Circuit breaker triggered for payment: {}", e.getMessage());
return PaymentResponse.builder()
.status(PaymentStatus.PENDING)
.message("Payment queued for processing")
.build();
}
}
// application.yml
resilience4j:
circuitbreaker:
instances:
paymentService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 5
2. Retry with Exponential Backoff
@Retry(name = "externalApi", fallbackMethod = "fallbackResponse")
public Response callExternalApi(Request request) {
return externalClient.call(request);
}
// application.yml
resilience4j:
retry:
instances:
externalApi:
maxAttempts: 3
waitDuration: 1s
exponentialBackoffMultiplier: 2
retryExceptions:
- java.io.IOException
- java.net.SocketTimeoutException
ignoreExceptions:
- com.example.BusinessException
3. Bulkhead (Isolation)
@Bulkhead(name = "inventoryService", type = Bulkhead.Type.THREADPOOL)
public InventoryResponse checkInventory(String productId) {
return inventoryClient.check(productId);
}
// application.yml
resilience4j:
bulkhead:
instances:
inventoryService:
maxConcurrentCalls: 20
maxWaitDuration: 500ms
thread-pool-bulkhead:
instances:
inventoryService:
maxThreadPoolSize: 10
coreThreadPoolSize: 5
queueCapacity: 100
4. Timeouts
@TimeLimiter(name = "slowService")
public CompletableFuture<Response> callSlowService() {
return CompletableFuture.supplyAsync(() -> slowClient.getData());
}
// application.yml
resilience4j:
timelimiter:
instances:
slowService:
timeoutDuration: 3s
cancelRunningFuture: true
Combined Pattern
@CircuitBreaker(name = "orderService")
@Retry(name = "orderService")
@Bulkhead(name = "orderService")
@TimeLimiter(name = "orderService")
public CompletableFuture<Order> createOrder(OrderRequest request) {
return CompletableFuture.supplyAsync(() -> orderClient.create(request));
}
17. API Security Best Practices
OAuth2/OIDC Implementation
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
return http.build();
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName("roles");
converter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
}
JWT Validation
@Component
public class JwtTokenValidator {
public void validateToken(String token) {
Jwt jwt = jwtDecoder.decode(token);
// Validate issuer
if (!validIssuers.contains(jwt.getIssuer())) {
throw new InvalidTokenException("Invalid issuer");
}
// Validate audience
if (!jwt.getAudience().contains(expectedAudience)) {
throw new InvalidTokenException("Invalid audience");
}
// Validate expiration
if (jwt.getExpiresAt().isBefore(Instant.now())) {
throw new TokenExpiredException("Token expired");
}
}
}
Rate Limiting (API Gateway / Application)
@Configuration
public class RateLimitConfig {
@Bean
public RateLimiter rateLimiter() {
return RateLimiter.of("api", RateLimiterConfig.custom()
.limitForPeriod(100)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofMillis(500))
.build());
}
}
// AWS API Gateway Throttling
{
"throttle": {
"rateLimit": 1000,
"burstLimit": 2000
}
}
Input Validation
@RestController
@Validated
public class UserController {
@PostMapping("/users")
public User createUser(@Valid @RequestBody CreateUserRequest request) {
return userService.create(request);
}
}
public record CreateUserRequest(
@NotBlank @Size(max = 100) String name,
@Email @NotBlank String email,
@Pattern(regexp = "^\\+?[1-9]\\d{1,14}$") String phone,
@Min(18) @Max(120) Integer age
) {}
Secrets Management (AWS)
@Configuration
public class SecretsConfig {
@Bean
public DataSource dataSource(SecretsManagerClient secretsClient) {
String secretJson = secretsClient.getSecretValue(
GetSecretValueRequest.builder()
.secretId("prod/database/credentials")
.build()
).secretString();
DbCredentials creds = objectMapper.readValue(secretJson, DbCredentials.class);
return DataSourceBuilder.create()
.url(creds.url())
.username(creds.username())
.password(creds.password())
.build();
}
}
18. Distributed Transactions & Saga
The Problem: Why Traditional Transactions Don't Work in Microservices
In a monolithic application, you can use ACID transactions:
BEGIN TRANSACTION;
INSERT INTO orders (id, user_id, total) VALUES (1, 100, 500);
UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 'ABC';
INSERT INTO payments (order_id, amount, status) VALUES (1, 500, 'SUCCESS');
COMMIT;
-- If anything fails, ROLLBACK undoes everything
But in microservices:
- Each service has its own database (database per service pattern)
- No single database to coordinate transactions
- Network calls can fail, timeout, or return partial results
- Services may be written in different languages/technologies
❌ Cannot do this across services:
BEGIN DISTRIBUTED_TRANSACTION;
OrderService.createOrder(); -- Different DB
InventoryService.reserve(); -- Different DB
PaymentService.charge(); -- Different DB
COMMIT;
Solutions for Distributed Transactions
| Solution | Description | Best For |
|---|---|---|
| Saga Pattern | Sequence of local transactions with compensations | Long-running business processes |
| Two-Phase Commit (2PC) | Coordinator locks all resources, then commits | Legacy systems, tight coupling OK |
| Outbox Pattern | Store events in same DB, publish asynchronously | Event-driven architectures |
| Event Sourcing | Store state as sequence of events | Audit-heavy, complex domains |
| TCC (Try-Confirm-Cancel) | Reserve resources, then confirm or cancel | Financial transactions |
Saga Pattern Deep Dive
The Saga pattern breaks a distributed transaction into a sequence of local transactions, each with a compensating action to undo changes if something fails.
Two Types of Saga
1. Choreography-based Saga (Event-Driven)
Services communicate through events. No central coordinator.
┌─────────────┐ OrderCreated ┌─────────────┐ PaymentSuccess ┌─────────────┐
│ Order │ ───────────────▶ │ Payment │ ─────────────────▶ │ Inventory │
│ Service │ │ Service │ │ Service │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ │ │
│ PaymentFailed │ InventoryFailed │
│ ◀──────────────────────────────│◀─────────────────────────────────│
│ │
▼ ▼
Cancel Order Refund Payment
(Compensation) (Compensation)
Pros:
- Loose coupling
- Simple for straightforward flows
- No single point of failure
Cons:
- Hard to track overall transaction state
- Complex error handling
- Difficult to debug
Implementation Example:
// Order Service - Publishes event after local transaction
@Service
public class OrderService {
@Transactional
public Order createOrder(OrderRequest request) {
Order order = orderRepository.save(new Order(request));
// Publish event (after commit via TransactionalEventListener)
eventPublisher.publish(new OrderCreatedEvent(order));
return order;
}
// Listen for failure events to compensate
@EventListener
public void handlePaymentFailed(PaymentFailedEvent event) {
orderRepository.updateStatus(event.getOrderId(), OrderStatus.CANCELLED);
}
}
// Payment Service - Listens and reacts
@Service
public class PaymentService {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
try {
Payment payment = processPayment(event.getOrder());
eventPublisher.publish(new PaymentSuccessEvent(payment));
} catch (PaymentException e) {
eventPublisher.publish(new PaymentFailedEvent(event.getOrderId(), e.getMessage()));
}
}
}
2. Orchestration-based Saga (Central Coordinator)
A central Saga Orchestrator controls the entire flow.
┌─────────────────────────┐
│ Saga Orchestrator │
│ (Order Saga Service) │
└───────────┬─────────────┘
│
┌────────────────────────────┼────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Order │ │ Payment │ │Inventory│
│ Service │ │ Service │ │ Service │
└─────────┘ └─────────┘ └─────────┘
Step 1: Create Order ──────────────────────────────────────────────▶
Step 2: Process Payment ───────────────────────────────────────────▶
Step 3: Reserve Inventory ─────────────────────────────────────────▶
On Failure: Execute compensating transactions in reverse order
Pros:
- Easy to understand and debug
- Centralized error handling
- Clear transaction state
Cons:
- Single point of failure (mitigate with HA)
- Orchestrator can become complex
- Tighter coupling to orchestrator
Implementation Example:
@Service
@Slf4j
public class OrderSagaOrchestrator {
private final OrderService orderService;
private final PaymentService paymentService;
private final InventoryService inventoryService;
private final ShippingService shippingService;
@Transactional
public SagaResult executeOrderSaga(OrderRequest request) {
SagaContext context = new SagaContext();
SagaState state = SagaState.STARTED;
try {
// Step 1: Create Order
log.info("Saga Step 1: Creating order");
Order order = orderService.create(request);
context.set("orderId", order.getId());
context.addCompensation("cancelOrder", () -> {
log.info("Compensating: Cancelling order {}", order.getId());
orderService.cancel(order.getId());
});
state = SagaState.ORDER_CREATED;
// Step 2: Reserve Inventory
log.info("Saga Step 2: Reserving inventory");
ReservationResult reservation = inventoryService.reserve(order.getItems());
context.set("reservationId", reservation.getId());
context.addCompensation("releaseInventory", () -> {
log.info("Compensating: Releasing inventory {}", reservation.getId());
inventoryService.release(reservation.getId());
});
state = SagaState.INVENTORY_RESERVED;
// Step 3: Process Payment
log.info("Saga Step 3: Processing payment");
PaymentResult payment = paymentService.charge(order.getPaymentInfo());
context.set("paymentId", payment.getId());
context.addCompensation("refundPayment", () -> {
log.info("Compensating: Refunding payment {}", payment.getId());
paymentService.refund(payment.getId());
});
state = SagaState.PAYMENT_PROCESSED;
// Step 4: Initiate Shipping
log.info("Saga Step 4: Initiating shipping");
ShippingResult shipping = shippingService.initiate(order);
state = SagaState.COMPLETED;
log.info("Saga completed successfully for order {}", order.getId());
return SagaResult.success(order, payment, shipping);
} catch (Exception e) {
log.error("Saga failed at state {}: {}", state, e.getMessage());
// Execute compensating transactions in reverse order
context.executeCompensationsInReverse();
return SagaResult.failed(state, e.getMessage());
}
}
}
// Saga Context to manage compensations
public class SagaContext {
private final Map<String, Object> data = new HashMap<>();
private final LinkedList<CompensationAction> compensations = new LinkedList<>();
public void addCompensation(String name, Runnable action) {
compensations.addFirst(new CompensationAction(name, action)); // Add to front for reverse order
}
public void executeCompensationsInReverse() {
for (CompensationAction compensation : compensations) {
try {
log.info("Executing compensation: {}", compensation.name());
compensation.action().run();
} catch (Exception e) {
log.error("Compensation {} failed: {}", compensation.name(), e.getMessage());
// Log but continue with other compensations
}
}
}
public void set(String key, Object value) { data.put(key, value); }
public <T> T get(String key) { return (T) data.get(key); }
}
public enum SagaState {
STARTED, ORDER_CREATED, INVENTORY_RESERVED, PAYMENT_PROCESSED, COMPLETED, FAILED
}
Outbox Pattern
Ensures reliable event publishing by storing events in the same database as the business data.
┌─────────────────────────────────────────────────────┐
│ Order Service │
│ ┌─────────────────────────────────────────────────┐│
│ │ Single Database Transaction ││
│ │ ┌─────────────┐ ┌─────────────────────┐ ││
│ │ │ Orders Table│ │ Outbox Table │ ││
│ │ │ INSERT order│ + │ INSERT event record │ ││
│ │ └─────────────┘ └─────────────────────┘ ││
│ └─────────────────────────────────────────────────┘│
└───────────────────────────┬─────────────────────────┘
│
┌───────▼───────┐
│ CDC / Poller │ (Debezium, Polling)
└───────┬───────┘
│
┌───────▼───────┐
│ Message Bus │ (Kafka, RabbitMQ)
└───────────────┘
Implementation:
@Service
public class OrderService {
@Transactional // Single transaction for both
public Order createOrder(OrderRequest request) {
// 1. Save order
Order order = orderRepository.save(new Order(request));
// 2. Save event to outbox (same transaction)
OutboxEvent event = OutboxEvent.builder()
.aggregateType("Order")
.aggregateId(order.getId())
.type("OrderCreated")
.payload(objectMapper.writeValueAsString(order))
.createdAt(Instant.now())
.build();
outboxRepository.save(event);
return order;
}
}
// Separate process polls outbox and publishes to Kafka
@Scheduled(fixedDelay = 1000)
public void publishOutboxEvents() {
List<OutboxEvent> events = outboxRepository.findUnpublished();
for (OutboxEvent event : events) {
try {
kafkaTemplate.send(event.getType(), event.getPayload());
event.markPublished();
outboxRepository.save(event);
} catch (Exception e) {
log.error("Failed to publish event: {}", event.getId());
}
}
}
TCC Pattern (Try-Confirm-Cancel)
A reservation-based pattern for financial transactions.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Account │ │ Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ TRY │ │ TRY │ │ TRY │
│ Reserve │ │ Reserve │ │ Reserve │
│ $500 │ │ 5 items │ │ slot │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
│ ALL TRY SUCCESSFUL? │
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ CONFIRM │ │ CONFIRM │ │ CONFIRM │
│ Deduct │ │ Deduct │ │ Assign │
│ $500 │ │ 5 items │ │ slot │
└───────────┘ └───────────┘ └───────────┘
OR (if any TRY fails)
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ CANCEL │ │ CANCEL │ │ CANCEL │
│ Release │ │ Release │ │ Release │
│ $500 │ │ 5 items │ │ slot │
└───────────┘ └───────────┘ └───────────┘
Implementation:
public interface TccService<T> {
T tryReserve(ReservationRequest request); // Reserve resources
void confirm(String reservationId); // Commit the reservation
void cancel(String reservationId); // Release the reservation
}
@Service
public class AccountTccService implements TccService<AccountReservation> {
@Override
public AccountReservation tryReserve(ReservationRequest request) {
Account account = accountRepository.findById(request.getAccountId());
if (account.getAvailableBalance() < request.getAmount()) {
throw new InsufficientFundsException();
}
// Reserve funds (don't deduct yet)
account.setReservedAmount(account.getReservedAmount() + request.getAmount());
accountRepository.save(account);
return new AccountReservation(UUID.randomUUID().toString(), request);
}
@Override
public void confirm(String reservationId) {
AccountReservation reservation = reservationRepository.findById(reservationId);
Account account = accountRepository.findById(reservation.getAccountId());
// Actually deduct the money
account.setBalance(account.getBalance() - reservation.getAmount());
account.setReservedAmount(account.getReservedAmount() - reservation.getAmount());
accountRepository.save(account);
reservationRepository.delete(reservation);
}
@Override
public void cancel(String reservationId) {
AccountReservation reservation = reservationRepository.findById(reservationId);
Account account = accountRepository.findById(reservation.getAccountId());
// Release reserved funds
account.setReservedAmount(account.getReservedAmount() - reservation.getAmount());
accountRepository.save(account);
reservationRepository.delete(reservation);
}
}
Idempotency Implementation
Ensures operations produce the same result when called multiple times.
Why Idempotency Matters:
Client ──Request──▶ Server ──Timeout──▶ Client retries
│
Did it succeed?
Did it fail?
Should I retry?
Without idempotency: Customer might be charged twice!
With idempotency: Safe to retry, server returns cached result
Implementation:
@Entity
@Table(name = "idempotency_keys")
public class IdempotencyRecord {
@Id
private String key; // Client-provided unique key
@Column(length = 10000)
private String responseBody; // Cached response
private Integer responseStatus; // HTTP status code
private Instant createdAt;
private Instant expiresAt; // TTL (e.g., 24 hours)
@Enumerated(EnumType.STRING)
private ProcessingStatus status; // PROCESSING, COMPLETED, FAILED
}
@Service
public class IdempotentPaymentService {
private final IdempotencyRepository idempotencyRepo;
private final PaymentProcessor paymentProcessor;
@Transactional
public ResponseEntity<PaymentResponse> processPayment(
String idempotencyKey,
PaymentRequest request) {
// 1. Check if already processed
Optional<IdempotencyRecord> existing = idempotencyRepo.findById(idempotencyKey);
if (existing.isPresent()) {
IdempotencyRecord record = existing.get();
// Still processing - return 409 Conflict
if (record.getStatus() == ProcessingStatus.PROCESSING) {
return ResponseEntity.status(409)
.body(new PaymentResponse("Request still processing"));
}
// Already completed - return cached response
return ResponseEntity.status(record.getResponseStatus())
.body(objectMapper.readValue(record.getResponseBody(), PaymentResponse.class));
}
// 2. Create record with PROCESSING status
IdempotencyRecord record = IdempotencyRecord.builder()
.key(idempotencyKey)
.status(ProcessingStatus.PROCESSING)
.createdAt(Instant.now())
.expiresAt(Instant.now().plus(24, ChronoUnit.HOURS))
.build();
idempotencyRepo.save(record);
try {
// 3. Process the payment
PaymentResponse response = paymentProcessor.process(request);
// 4. Update record with result
record.setStatus(ProcessingStatus.COMPLETED);
record.setResponseStatus(200);
record.setResponseBody(objectMapper.writeValueAsString(response));
idempotencyRepo.save(record);
return ResponseEntity.ok(response);
} catch (Exception e) {
// 5. Update record with failure
record.setStatus(ProcessingStatus.FAILED);
record.setResponseStatus(500);
record.setResponseBody(objectMapper.writeValueAsString(
new PaymentResponse("Payment failed: " + e.getMessage())));
idempotencyRepo.save(record);
throw e;
}
}
}
// Controller
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@PostMapping
public ResponseEntity<PaymentResponse> createPayment(
@RequestHeader("Idempotency-Key") String idempotencyKey,
@Valid @RequestBody PaymentRequest request) {
return idempotentPaymentService.processPayment(idempotencyKey, request);
}
}
Client-side usage:
// Frontend generates unique key
const idempotencyKey = crypto.randomUUID();
// Safe to retry on network failure
async function createPayment(paymentData) {
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const response = await fetch('/api/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey // Same key for all retries
},
body: JSON.stringify(paymentData)
});
if (response.ok || response.status !== 409) {
return response.json();
}
// 409 = still processing, wait and retry
await sleep(1000 * attempt);
} catch (error) {
if (attempt === 3) throw error;
await sleep(1000 * attempt);
}
}
}
Choosing the Right Pattern
| Scenario | Recommended Pattern |
|---|---|
| E-commerce order flow | Saga (Orchestration) - Clear steps, easy rollback |
| Event-driven microservices | Saga (Choreography) + Outbox - Loose coupling |
| Financial transactions | TCC - Strong consistency guarantees |
| Payment processing | Saga + Idempotency - Safe retries |
| Legacy system integration | Two-Phase Commit - When supported |
| Audit-heavy domains | Event Sourcing - Complete history |
Key Takeaways
- Avoid distributed transactions when possible - Design services to be eventually consistent
- Sagas are the most common solution - Use orchestration for complex flows, choreography for simple ones
- Always implement idempotency - Essential for safe retries and reliability
- Outbox pattern ensures reliable messaging - No lost events
- Compensating transactions must be idempotent - They may be called multiple times
- Monitor saga state - Track pending, completed, and failed sagas
19. Quality Gates for Financial Systems
Pipeline Quality Gates
quality_gates:
# Gate 1: Code Quality
code_quality:
- lint_errors: 0
- type_check_errors: 0
- code_smells: < 10 per 1000 LOC
- duplications: < 3%
# Gate 2: Test Coverage
test_coverage:
- unit_test_coverage: >= 80%
- branch_coverage: >= 75%
- critical_path_coverage: 100%
- mutation_score: >= 70%
# Gate 3: Security
security:
- critical_vulnerabilities: 0
- high_vulnerabilities: 0
- medium_vulnerabilities: < 5
- secrets_detected: 0
- SAST_issues: 0 critical/high
- DAST_issues: 0 critical/high
# Gate 4: Performance
performance:
- api_response_p95: < 500ms
- api_response_p99: < 1000ms
- memory_leak_detected: false
- load_test_error_rate: < 0.1%
# Gate 5: Compliance
compliance:
- license_violations: 0
- PCI_DSS_checks: pass
- SOC2_controls: pass
- audit_logging: enabled
Implementation in CI/CD
# GitLab CI Example
stages:
- build
- test
- security
- quality-gate
- deploy
quality_gate:
stage: quality-gate
script:
- |
# Fetch metrics
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
CRITICAL_VULNS=$(cat security-report.json | jq '.criticalCount')
SONAR_STATUS=$(curl -s "$SONAR_URL/api/qualitygates/project_status?projectKey=$PROJECT")
# Evaluate gates
if [ "$COVERAGE" -lt 80 ]; then
echo "FAILED: Coverage $COVERAGE% < 80%"
exit 1
fi
if [ "$CRITICAL_VULNS" -gt 0 ]; then
echo "FAILED: $CRITICAL_VULNS critical vulnerabilities found"
exit 1
fi
if [ "$(echo $SONAR_STATUS | jq -r '.projectStatus.status')" != "OK" ]; then
echo "FAILED: SonarQube quality gate failed"
exit 1
fi
echo "All quality gates passed!"
rules:
- if: $CI_COMMIT_BRANCH == "main"
Financial System Specific Gates
- Four-Eyes Principle: Require 2 approvers for production deployments
- Change Window Enforcement: Only deploy during approved windows
- Rollback Readiness: Automated rollback verification
- Data Integrity Checks: Pre/post deployment validation
- Regulatory Compliance: Automated compliance checking
20. Performance Balancing Strategy
Frontend Performance
| Optimization | Technique |
|---|---|
| Bundle Size | Code splitting, tree shaking, lazy loading |
| Caching | Service workers, HTTP cache headers |
| Rendering | SSR/SSG for initial load, skeleton screens |
| Images | WebP format, lazy loading, CDN |
| Critical Path | Inline critical CSS, defer non-essential JS |
// Code Splitting Example
const Dashboard = lazy(() => import('./Dashboard'));
const Reports = lazy(() => import('./Reports'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
);
}
BFF Latency Optimization
| Optimization | Technique |
|---|---|
| Parallel Calls | CompletableFuture, reactive streams |
| Caching | Redis for frequently accessed data |
| Response Shaping | Return only needed fields |
| Connection Pooling | HTTP client connection reuse |
| Compression | gzip/brotli for responses |
// Parallel API calls
public DashboardResponse getDashboard(String userId) {
CompletableFuture<Profile> profile = CompletableFuture.supplyAsync(
() -> profileService.get(userId), executor);
CompletableFuture<List<Transaction>> transactions = CompletableFuture.supplyAsync(
() -> transactionService.getRecent(userId), executor);
CompletableFuture<Balance> balance = CompletableFuture.supplyAsync(
() -> accountService.getBalance(userId), executor);
return CompletableFuture.allOf(profile, transactions, balance)
.thenApply(v -> DashboardResponse.builder()
.profile(profile.join())
.transactions(transactions.join())
.balance(balance.join())
.build())
.join();
}
Backend Data Consistency
| Pattern | Use Case |
|---|---|
| Eventual Consistency | Read replicas, caching, search indexes |
| Strong Consistency | Financial transactions, balances |
| CQRS | Separate read/write models |
| Event Sourcing | Audit trail, temporal queries |
Balancing Strategy Matrix
┌─────────────────────────────────────────┐
│ Performance vs Consistency │
├─────────────────────────────────────────┤
High │ Cache + Eventual │ Cache + Strong │
Performance │ (Product catalog) │ (User sessions) │
├─────────────────────┼───────────────────┤
Balanced │ Async Processing │ Sync + Retries │
│ (Notifications) │ (Order creation) │
├─────────────────────┼───────────────────┤
High │ Queue + Saga │ 2PC / Saga │
Consistency │ (Inventory) │ (Payments) │
└─────────────────────┴───────────────────┘
Eventual Strong
Consistency Consistency
Practical Guidelines
- Identify Critical Paths: Which operations MUST be strongly consistent?
- Accept Eventual Consistency: For non-critical reads (user preferences, analytics)
- Cache Strategically:
- Cache at CDN for static content
- Cache at BFF for aggregated data
- Cache at service for frequent queries
- Set SLOs: Define acceptable latency per endpoint
- Monitor & Iterate: Use APM to identify bottlenecks
Performance Budget Example:
┌──────────────────────────────────────────────────────────┐
│ User Action: View Account Dashboard │
├──────────────────────────────────────────────────────────┤
│ FE Initial Load: < 1.5s (LCP) │
│ FE Time to Interactive: < 2.5s │
│ BFF Response Time: < 200ms (p95) │
│ BE Service Calls: < 100ms each (parallel) │
│ Database Queries: < 50ms │
│ Total User Wait: < 2s │
└──────────────────────────────────────────────────────────┘
Summary
This guide covers the essential topics for modern full-stack development with React, Java, and cloud-native architectures. Key takeaways:
- React: Understand component composition, state management, and performance optimization
- TypeScript: Leverage type safety for better developer experience
- Testing: Use the test pyramid approach with appropriate tools
- CI/CD: Automate quality gates and deployments
- Microservices: Design for resilience, observability, and loose coupling
- Cloud: Use managed services, implement autoscaling, and prioritize security
- Resilience: Implement circuit breakers, retries, and bulkheads
- Security: Defense in depth with OAuth2, validation, and secrets management
- Distributed Systems: Use Saga pattern and ensure idempotency
- Performance: Balance user experience with data consistency requirements
Quick Reference Answers
Q1: Describe the roles of components, props, and state in React and when to split a component.
Answer:
- Components: Reusable, self-contained building blocks that encapsulate UI logic and structure. They can be functional (preferred) or class-based.
- Props: Read-only data passed from parent to child components. They enable customization and flow unidirectionally (top-down).
- State: Mutable data managed within a component that triggers re-renders when changed. Managed via useState hook.
- When to Split: Split when a component exceeds 200-300 lines, has multiple responsibilities, contains repeated patterns, has complex nested conditionals, or when parts update at different frequencies.
Q2: What are the main differences between ES6 JavaScript and TypeScript in a React project?
Answer:
| Aspect | ES6 JavaScript | TypeScript |
|---|---|---|
| Type System | Dynamic | Static with annotations |
| Compile-time Errors | None | Catches errors before runtime |
| IDE Support | Basic | Rich IntelliSense & refactoring |
| Interfaces/Generics | Not supported | Full support |
| Build Step | Optional | Required (tsc) |
| Refactoring | Error-prone | Safe with type checking |
TypeScript provides better maintainability, self-documenting code, and catches bugs earlier in development.
Example 1: Props Definition
// ES6 JavaScript - No type safety
function UserCard({ name, age, onUpdate }) {
return <div onClick={() => onUpdate(name)}>{name} - {age}</div>;
}
// Problem: No way to know what props are expected or their types
// Can accidentally pass wrong types: <UserCard name={123} age="twenty" />
// TypeScript - Full type safety
interface UserCardProps {
name: string;
age: number;
email?: string; // Optional prop
onUpdate: (name: string) => void;
}
const UserCard: React.FC<UserCardProps> = ({ name, age, email, onUpdate }) => {
return <div onClick={() => onUpdate(name)}>{name} - {age}</div>;
};
// Benefit: IDE shows errors immediately if wrong props are passed
// <UserCard name={123} /> // ❌ Error: Type 'number' is not assignable to type 'string'
Example 2: State Management
// ES6 JavaScript
const [user, setUser] = useState(null);
// Later...
console.log(user.name); // ❌ Runtime error if user is null!
// TypeScript
interface User {
id: number;
name: string;
email: string;
}
const [user, setUser] = useState<User | null>(null);
// Later...
console.log(user.name); // ❌ Compile error: Object is possibly 'null'
console.log(user?.name); // ✅ Safe access with optional chaining
Example 3: API Response Handling
// ES6 JavaScript - No guarantee of response shape
async function fetchUsers() {
const response = await fetch('/api/users');
const data = await response.json();
return data; // What properties does data have? 🤷
}
// Using the data
const users = await fetchUsers();
users.forEach(u => console.log(u.nmae)); // Typo! Won't catch until runtime
// TypeScript - Typed API responses
interface ApiResponse<T> {
data: T;
meta: { total: number; page: number };
}
interface User {
id: number;
name: string;
email: string;
}
async function fetchUsers(): Promise<ApiResponse<User[]>> {
const response = await fetch('/api/users');
const data: ApiResponse<User[]> = await response.json();
return data;
}
// Using the data
const { data: users } = await fetchUsers();
users.forEach(u => console.log(u.nmae)); // ❌ Error: Property 'nmae' does not exist. Did you mean 'name'?
Example 4: Event Handlers
// ES6 JavaScript
function handleChange(e) {
console.log(e.target.value); // Works, but no autocomplete
console.log(e.target.checked); // Is this a checkbox? Who knows!
}
// TypeScript - Precise event types
function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value); // ✅ Autocomplete works
}
function handleSelectChange(e: React.ChangeEvent<HTMLSelectElement>) {
console.log(e.target.value);
console.log(e.target.selectedIndex); // ✅ Select-specific properties available
}
function handleFormSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
}
Example 5: Enums and Union Types
// ES6 JavaScript - Magic strings everywhere
function setStatus(status) {
if (status === 'pending') { /* ... */ }
else if (status === 'aprovved') { /* Typo! */ }
}
setStatus('pnding'); // Another typo, no error
// TypeScript - Type-safe status values
type OrderStatus = 'pending' | 'approved' | 'rejected' | 'shipped';
function setStatus(status: OrderStatus) {
if (status === 'pending') { /* ... */ }
else if (status === 'aprovved') { /* ❌ Error: not in union type */ }
}
setStatus('pnding'); // ❌ Error: Argument of type '"pnding"' is not assignable
// Or use enums
enum PaymentStatus {
Pending = 'PENDING',
Completed = 'COMPLETED',
Failed = 'FAILED',
}
function processPayment(status: PaymentStatus) {
switch (status) {
case PaymentStatus.Pending: // ✅ Autocomplete available
break;
case PaymentStatus.Completed:
break;
// TypeScript warns if you miss a case!
}
}
Example 6: Custom Hooks
// ES6 JavaScript
function useAuth() {
const [user, setUser] = useState(null);
const login = (email, password) => { /* ... */ };
const logout = () => { /* ... */ };
return { user, login, logout };
}
// No way to know what this hook returns without reading the code
// TypeScript - Self-documenting hooks
interface User {
id: string;
email: string;
role: 'admin' | 'user';
}
interface AuthContext {
user: User | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
function useAuth(): AuthContext {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const login = async (email: string, password: string): Promise<void> => {
// Implementation
};
const logout = (): void => {
setUser(null);
};
return { user, isLoading, login, logout };
}
// Hover over useAuth() to see exactly what it returns!
Q3: Name three accessibility best practices when building React UIs.
Answer:
- Semantic HTML: Use proper HTML elements (
- ARIA Labels and Roles: Add aria-label, aria-describedby, and role attributes to provide context for assistive technologies, especially for icon-only buttons and dynamic content.
- Keyboard Navigation: Ensure all interactive elements are focusable (tabIndex), respond to keyboard events (Enter, Escape), and maintain visible focus indicators.
Example 1: Semantic HTML Elements
// ❌ Bad - Using divs for everything
function BadNavigation() {
return (
<div className="nav">
<div className="nav-item" onClick={goHome}>Home</div>
<div className="nav-item" onClick={goAbout}>About</div>
<div className="nav-item" onClick={goContact}>Contact</div>
</div>
);
}
// ✅ Good - Semantic HTML
function GoodNavigation() {
return (
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
);
}
// ❌ Bad - Div as button
<div className="btn" onClick={handleSubmit}>Submit</div>
// ✅ Good - Actual button element
<button type="submit" onClick={handleSubmit}>Submit</button>
// ❌ Bad - Div as input container with no structure
<div>
<span>Email</span>
<input type="email" />
</div>
// ✅ Good - Proper label association
<div>
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" />
</div>
Example 2: ARIA Labels and Roles
// Icon-only buttons need labels
// ❌ Bad - No context for screen readers
<button onClick={onClose}>
<CloseIcon />
</button>
// ✅ Good - With aria-label
<button onClick={onClose} aria-label="Close dialog">
<CloseIcon aria-hidden="true" />
</button>
// ✅ Alternative - With visually hidden text
<button onClick={onClose}>
<CloseIcon aria-hidden="true" />
<span className="sr-only">Close dialog</span>
</button>
// Live regions for dynamic content updates
function NotificationBanner({ message }) {
return (
// aria-live announces changes to screen readers
<div
role="alert"
aria-live="polite"
aria-atomic="true"
>
{message}
</div>
);
}
// Loading states
function DataTable({ isLoading, data }) {
return (
<div
aria-busy={isLoading}
aria-live="polite"
>
{isLoading ? (
<div role="status">
<span className="sr-only">Loading data...</span>
<Spinner aria-hidden="true" />
</div>
) : (
<table>{/* data */}</table>
)}
</div>
);
}
// Descriptive links
// ❌ Bad - Ambiguous link text
<a href="/docs">Click here</a>
<a href="/report">Read more</a>
// ✅ Good - Descriptive link text
<a href="/docs">View documentation</a>
<a href="/report">Read the full quarterly report</a>
// ✅ Good - With aria-describedby for additional context
<article>
<h2 id="article-title">Understanding React Hooks</h2>
<p id="article-desc">A comprehensive guide to useState and useEffect...</p>
<a href="/article/1" aria-describedby="article-title article-desc">
Read full article
</a>
</article>
Example 3: Keyboard Navigation
// Modal with proper focus management
function AccessibleModal({ isOpen, onClose, children }) {
const modalRef = useRef(null);
const previousFocusRef = useRef(null);
useEffect(() => {
if (isOpen) {
// Store current focus to restore later
previousFocusRef.current = document.activeElement;
// Move focus to modal
modalRef.current?.focus();
} else if (previousFocusRef.current) {
// Restore focus when modal closes
previousFocusRef.current.focus();
}
}, [isOpen]);
// Handle Escape key
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
onClose();
}
};
if (!isOpen) return null;
return (
<div
className="modal-overlay"
onClick={onClose}
onKeyDown={handleKeyDown}
>
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
tabIndex={-1}
onClick={(e) => e.stopPropagation()}
>
<h2 id="modal-title">Modal Title</h2>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
}
// Custom interactive component with keyboard support
function AccessibleDropdown({ options, value, onChange }) {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(0);
const handleKeyDown = (e) => {
switch (e.key) {
case 'Enter':
case ' ':
e.preventDefault();
if (isOpen) {
onChange(options[activeIndex]);
setIsOpen(false);
} else {
setIsOpen(true);
}
break;
case 'ArrowDown':
e.preventDefault();
if (isOpen) {
setActiveIndex((prev) => Math.min(prev + 1, options.length - 1));
} else {
setIsOpen(true);
}
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex((prev) => Math.max(prev - 1, 0));
break;
case 'Escape':
setIsOpen(false);
break;
case 'Tab':
setIsOpen(false);
break;
}
};
return (
<div
role="combobox"
aria-expanded={isOpen}
aria-haspopup="listbox"
aria-controls="dropdown-list"
tabIndex={0}
onKeyDown={handleKeyDown}
onClick={() => setIsOpen(!isOpen)}
>
<span>{value || 'Select an option'}</span>
{isOpen && (
<ul id="dropdown-list" role="listbox">
{options.map((option, index) => (
<li
key={option}
role="option"
aria-selected={index === activeIndex}
onClick={() => {
onChange(option);
setIsOpen(false);
}}
>
{option}
</li>
))}
</ul>
)}
</div>
);
}
Example 4: Focus Indicators
/* ❌ Bad - Removing focus outline completely */
button:focus {
outline: none;
}
/* ✅ Good - Custom visible focus indicator */
button:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.6);
}
/* ✅ Better - Using focus-visible for keyboard-only focus */
button:focus {
outline: none;
}
button:focus-visible {
outline: 2px solid #4A90D9;
outline-offset: 2px;
}
Example 5: Skip Links
// Allow keyboard users to skip repetitive navigation
function Layout({ children }) {
return (
<>
<a href="#main-content" className="skip-link">
Skip to main content
</a>
<header>
<Navigation /> {/* Many links to tab through */}
</header>
<main id="main-content" tabIndex={-1}>
{children}
</main>
</>
);
}
// CSS for skip link
// .skip-link {
// position: absolute;
// left: -9999px;
// z-index: 999;
// }
// .skip-link:focus {
// left: 10px;
// top: 10px;
// background: #000;
// color: #fff;
// padding: 8px 16px;
// }
Example 6: Form Accessibility
function AccessibleForm() {
const [errors, setErrors] = useState({});
return (
<form aria-describedby="form-instructions">
<p id="form-instructions">
Fields marked with * are required
</p>
{/* Required field with error handling */}
<div>
<label htmlFor="email">
Email <span aria-hidden="true">*</span>
<span className="sr-only">(required)</span>
</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && (
<span id="email-error" role="alert" className="error">
{errors.email}
</span>
)}
</div>
{/* Password with requirements */}
<div>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
aria-describedby="password-requirements"
/>
<p id="password-requirements" className="hint">
Must be at least 8 characters with one number and one special character
</p>
</div>
{/* Accessible button */}
<button type="submit">
Create Account
</button>
</form>
);
}
Example 7: Images and Media
// Informative images need alt text
<img src="chart.png" alt="Sales chart showing 25% growth in Q4 2025" />
// Decorative images should be hidden
<img src="decorative-border.png" alt="" role="presentation" />
// Complex images with detailed descriptions
<figure>
<img
src="architecture-diagram.png"
alt="System architecture diagram"
aria-describedby="diagram-desc"
/>
<figcaption id="diagram-desc">
The diagram shows three microservices connected via an API gateway.
The User Service handles authentication, the Order Service manages
transactions, and the Notification Service sends emails and SMS.
</figcaption>
</figure>
// Video with captions
<video controls>
<source src="tutorial.mp4" type="video/mp4" />
<track kind="captions" src="captions.vtt" srcLang="en" label="English" />
<track kind="descriptions" src="descriptions.vtt" srcLang="en" label="Audio descriptions" />
</video>
Screen Reader Only Utility Class:
/* Visually hidden but accessible to screen readers */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Q4: What are the minimal CI steps for a Front-End project before deployment?
Answer:
- Install Dependencies: npm ci for reproducible builds
- Linting: ESLint to enforce code quality standards
- Type Checking: TypeScript compilation check (tsc --noEmit)
- Unit Tests: Run Jest/Vitest tests with coverage reporting
- Build: Production build to verify compilation succeeds
Optional additions: Security audit (npm audit), bundle size analysis, and E2E tests for critical paths.
Q5: What are the characteristics of a good RESTful API?
Answer:
- Stateless: Each request contains all necessary information
- Resource-Based URLs: Use nouns (/users/{id}), not verbs (/getUser)
- Proper HTTP Methods: GET (read), POST (create), PUT (replace), PATCH (partial update), DELETE (remove)
- Consistent Response Format: Standardized structure with data, meta, errors
- Appropriate Status Codes: 200, 201, 204, 400, 401, 403, 404, 500
- Versioning: /api/v1/ for backward compatibility
- HATEOAS: Include hypermedia links for discoverability
- Pagination, Filtering, Sorting: Query parameters for large datasets
Q6: Why use a Java BFF (Backend for Frontend) and what are the main benefits?
Answer:
Origin: The BFF pattern was coined by Sam Newman at SoundCloud around 2015 to solve problems with microservices architectures where frontends had to make multiple API calls and handle complex data aggregation.
The Problem Before BFF:
- Frontend needed 5-10 API calls to render one page
- Over-fetching/under-fetching data
- Different clients (web, mobile, TV) needed different data shapes
- Internal service details exposed to clients
- Complex data transformation logic in the browser
What is BFF: A dedicated backend layer tailored to specific frontend needs, sitting between the frontend and microservices.
When to Use BFF:
- Multiple clients with different data needs
- Frontend making 5+ API calls per page
- Need to hide internal APIs for security
- Different teams own frontend and backend
Main Benefits:
- API Aggregation: Combine multiple backend calls into a single response
- Response Shaping: Transform data specifically for UI needs
- Security Layer: Handle authentication, hide internal service details
- Caching: Cache frequently accessed data to reduce backend load
- Protocol Translation: Convert gRPC/SOAP to REST/GraphQL
- Resilience: Implement circuit breakers, retries, and fallbacks
- Rate Limiting: Protect downstream services from traffic spikes
Why Java: JVM performance, strong typing, mature ecosystem (Spring Boot, Resilience4j), excellent async support (CompletableFuture), and enterprise team familiarity.
BFF vs API Gateway: API Gateway handles routing/auth (infra team owns), BFF handles data aggregation/transformation (frontend team owns). Often used together: Client → API Gateway → BFF → Microservices
Q7: How do you apply Scrum/Agile to ensure quality?
Answer:
- Definition of Done (DoD): Include code review, tests, coverage thresholds, documentation, and accessibility requirements
- Sprint Planning: Include testing effort in story estimates
- Daily Standups: Surface blockers and quality issues early
- Pair Programming: Real-time code review and knowledge sharing
- Test-Driven Development (TDD): Write tests before implementation
- Retrospectives: Identify quality improvement areas and act on them
- Continuous Integration: Automated quality gates on every commit
- Technical Debt Sprints: Allocate capacity for refactoring and improvements
- Shift-Left Testing: Test early in the development cycle
Q8: How do you optimize unnecessary re-renders in React?
Answer:
How React Re-rendering Works:
- Trigger: State change (setState), props change, parent re-render, or context change
- Render Phase: React calls component function to create new Virtual DOM
- Reconciliation: React diffs new vs old Virtual DOM
- Commit Phase: React updates only changed parts in actual DOM
Key Concept - Shallow Comparison: React uses shallow comparison to detect changes:
- Primitives: Compares values (5 === 5 → true)
- Objects/Arrays: Compares references, NOT contents ({a:1} === {a:1} → false, different memory references)
Why This Causes Problems:
// Every render creates NEW objects with NEW references
const user = { name: 'John' }; // New reference each render
const items = [1, 2, 3]; // New reference each render
const onClick = () => {}; // New reference each render
// Even with React.memo, child re-renders because references changed!
Solutions:
| Problem | Solution |
|---|---|
| Child re-renders when props unchanged | React.memo(Component) |
| Expensive calculation every render | useMemo(() => calculation, [deps]) |
| Function prop causes re-render | useCallback(() => fn, [deps]) |
| State affects unrelated components | State Colocation (move state down) |
| Rendering thousands of items | Virtualization (react-window) |
| Inline objects/arrays in props | Define outside component or use useMemo |
Debugging: Use React DevTools Profiler to identify which components re-render and why.
Golden Rule: Don't optimize prematurely—profile first, then optimize actual bottlenecks.
Q9: When do you use Jest vs Selenium/E2E for Front-End testing?
Answer:
| Use Jest When: | Use Selenium/E2E When: |
|---|---|
| Testing individual functions/components | Testing complete user journeys |
| Fast feedback needed (ms per test) | Cross-browser compatibility required |
| Testing hooks and component behavior | Integration with real APIs needed |
| Mocking external dependencies | Visual regression testing |
| Running in CI frequently | Validating critical paths (login, checkout) |
| 70-80% of your test suite | 10-20% of your test suite |
Rule of Thumb: Jest for unit/component tests (fast, reliable), E2E for critical user flows (slower, more realistic).
Q10: When would you choose GraphQL over REST in microservices?
Answer:
Choose GraphQL when:
- Multiple clients have different data needs
- Data is deeply nested or highly relational
- Frontend needs rapid iteration without backend changes
- Reducing over-fetching/under-fetching is critical
- Real-time subscriptions are required
Choose REST when:
- Simple CRUD operations dominate
- File uploads/downloads are common
- HTTP caching is important
- Team is more familiar with REST
- Building public APIs
In Microservices: GraphQL works well as a gateway (federation) that aggregates multiple services, providing a unified API while keeping services independently deployable.
Q11: What technical characteristics should a well-designed microservice have?
Answer:
- Single Responsibility: One business capability per service
- Independently Deployable: Deploy without affecting other services
- Loosely Coupled: Minimal dependencies on other services
- Owns Its Data: Dedicated database per service (no shared databases)
- API-First: Well-defined contracts (OpenAPI/AsyncAPI)
- Observable: Built-in logging, metrics, and distributed tracing
- Resilient: Handles failures gracefully (circuit breakers, retries)
- Stateless: No local session state (externalize to cache/DB)
- Containerized: Runs in Docker/Kubernetes for portability
- Health Checks: Liveness and readiness probes for orchestration
Q12: How do you manage unit/component test coverage and mocking?
Answer:
Coverage Strategy (Test Pyramid):
- Unit Tests: 70% (business logic, utilities)
- Integration Tests: 20% (service boundaries)
- E2E Tests: 10% (critical paths)
Coverage Targets:
- Business Logic: 90%+
- Components: 80%+
- Overall: 80% line coverage, 75% branch coverage
Mocking Approaches:
- Jest Mocks: jest.mock() for modules, jest.spyOn() for functions
- MSW (Mock Service Worker): Mock API responses at network level
- Component Mocks: Replace heavy components with lightweight test doubles
- Dependency Injection: Design for testability by injecting dependencies
Enforcement: Use Jest's coverageThreshold in configuration to fail CI if coverage drops below thresholds.
Q13: Describe a standard CI/CD pipeline for FE & BE from commit to deployment.
Answer:
Frontend Pipeline:
- Build: Checkout → Install deps (npm ci) → Lint → Type check → Build
- Test: Unit tests → Coverage report → SonarQube scan
- Security: npm audit → Snyk/OWASP scan
- Deploy Staging: Upload to S3 → Invalidate CloudFront → E2E tests
- Deploy Production: Manual approval → Deploy → Smoke tests → Notify
Backend Pipeline:
- Build: Checkout → Maven compile → Package → Docker build
- Test: Unit tests → Integration tests → Coverage (JaCoCo) → SonarQube
- Security: OWASP dependency-check → Trivy container scan → Secrets scan
- Deploy: Push to ECR → Update ECS task → Health check → Rollback on failure
Key Practices: Artifacts passed between stages, parallel jobs where possible, manual approval gates for production.
Q14: How do you deploy a React FE and Java BE to AWS?
Answer:
React Frontend:
- S3: Host static files (HTML, JS, CSS)
- CloudFront: CDN for global distribution, HTTPS, caching
- Route 53: DNS management
- Certificate Manager: SSL/TLS certificates
- WAF: Web Application Firewall for security
Java Backend:
- ECR: Store Docker images
- ECS Fargate (or EKS): Run containers without managing servers
- ALB: Load balancing and health checks
- RDS/Aurora: Managed database
- ElastiCache: Redis for caching/sessions
- Secrets Manager: Store credentials securely
Deployment Flow: CI builds artifacts → Push to S3/ECR → Update CloudFront/ECS → Health checks → Rollback if needed.
Q15: For a cloud-first banking system, which AWS services, autoscaling, and observability would you choose?
Answer:
Core Services:
- Compute: EKS/ECS Fargate (containerized, auto-scaling)
- Database: Aurora PostgreSQL (Multi-AZ, ACID compliance)
- Cache: ElastiCache Redis
- Queue: SQS/EventBridge for async processing
- API: API Gateway with throttling
Auto-Scaling:
- ECS: Target tracking (CPU 70%, Memory 75%)
- Aurora: Auto-scaling read replicas (2-15)
- Step scaling for traffic spikes
Observability:
- Logging: CloudWatch Logs + Log Insights (or OpenSearch)
- Metrics: CloudWatch + Prometheus/Grafana
- Tracing: X-Ray or OpenTelemetry
- Alerting: CloudWatch Alarms → SNS → PagerDuty
- APM: X-Ray Service Map
Security: WAF, Shield, KMS encryption, CloudTrail audit logs, VPC with private subnets.
Q16: In a Java BFF, how do you implement circuit breaker, retry/backoff, bulkhead, and timeouts?
Answer: Use Resilience4j library:
Circuit Breaker:
@CircuitBreaker(name = "service", fallbackMethod = "fallback")
public Response call() { return client.call(); }
// Config: slidingWindowSize=10, failureRateThreshold=50%, waitDurationInOpenState=30s
Retry with Exponential Backoff:
@Retry(name = "service")
// Config: maxAttempts=3, waitDuration=1s, exponentialBackoffMultiplier=2
Bulkhead (Isolation):
@Bulkhead(name = "service", type = Bulkhead.Type.THREADPOOL)
// Config: maxConcurrentCalls=20, maxThreadPoolSize=10, queueCapacity=100
Timeout:
@TimeLimiter(name = "service")
public CompletableFuture<Response> call() { ... }
// Config: timeoutDuration=3s
Combine all patterns on critical methods for maximum resilience.
Q17: How do you secure APIs with OAuth2/OIDC, JWT, rate limiting, validation, and secrets management on AWS?
Answer:
OAuth2/OIDC: Configure Spring Security with oauth2ResourceServer(), validate JWTs against identity provider (Cognito, Okta).
JWT Validation: Verify issuer, audience, expiration, and signature. Extract roles/permissions from claims.
Rate Limiting:
- API Gateway: Configure throttling (1000 req/s, burst 2000)
- Application: Resilience4j RateLimiter
Input Validation: Use Bean Validation (@Valid, @NotBlank, @Email, @Pattern) on request DTOs.
Secrets Management:
- Store credentials in AWS Secrets Manager
- Rotate secrets automatically
- Never commit secrets to code
- Use IAM roles for service-to-service auth
Additional: WAF rules, TLS 1.3, security headers, audit logging.
Q18: How do you handle distributed transactions with Saga and ensure idempotency?
Answer:
The Problem: In microservices, each service has its own database, so traditional ACID transactions don't work across services. We need patterns that handle failures gracefully.
Distributed Transaction Solutions:
| Pattern | Description | Best For |
|---|---|---|
| Saga | Sequence of local transactions with compensations | Long-running business processes |
| Outbox | Store events in same DB, publish asynchronously | Event-driven architectures |
| TCC | Try-Confirm-Cancel (reservation-based) | Financial transactions |
| Event Sourcing | Store state as sequence of events | Audit-heavy domains |
Saga Pattern (Two Types):
-
Choreography: Services communicate via events, no central coordinator
- Pros: Loose coupling, no single point of failure
- Cons: Hard to track state, complex debugging
-
Orchestration: Central coordinator manages the flow
- Pros: Clear flow, easy debugging, centralized error handling
- Cons: Single point of failure, tighter coupling
Saga Implementation:
try {
order = orderService.create(request);
context.addCompensation(() -> orderService.cancel(order.getId()));
inventoryService.reserve(order.getItems());
context.addCompensation(() -> inventoryService.release(order.getItems()));
paymentService.charge(order);
// Success
} catch (Exception e) {
context.executeCompensationsInReverse(); // Rollback in reverse order
}
TCC Pattern (Try-Confirm-Cancel):
- Try: Reserve resources in all services
- Confirm: If all succeed, commit all reservations
- Cancel: If any fail, release all reservations
Outbox Pattern: Write events to an outbox table in the same transaction as business data, then publish asynchronously. Guarantees no lost events.
Idempotency (Essential for Safe Retries):
- Accept Idempotency-Key header from client
- Store key + response in database before processing
- Check for existing key before processing new requests
- Return cached response if key already exists
- Set TTL on idempotency records (e.g., 24 hours)
Key Principles:
- Design for eventual consistency when possible
- Compensating transactions must also be idempotent
- Always monitor saga states (pending, completed, failed)
- Log everything for debugging distributed flows
Q19: How do you design quality gates in the pipeline for financial-grade systems?
Answer:
Gate 1 - Code Quality:
- Zero lint/type errors
- Code smells < 10 per 1000 LOC
- Duplication < 3%
Gate 2 - Test Coverage:
- Unit test coverage ≥ 80%
- Branch coverage ≥ 75%
- Critical paths: 100%
Gate 3 - Security:
- Zero critical/high vulnerabilities
- No secrets in code
- SAST/DAST clean
Gate 4 - Performance:
- API p95 < 500ms
- No memory leaks
- Load test error rate < 0.1%
Gate 5 - Compliance:
- License compliance
- PCI-DSS/SOC2 controls pass
- Audit logging enabled
Additional Controls:
- Four-eyes principle (2 approvers for production)
- Change window enforcement
- Automated rollback readiness
- Pre/post deployment data integrity checks
Q20: How do you balance FE performance, BFF latency, and BE data consistency?
Answer:
Frontend Performance:
- Code splitting and lazy loading
- CDN caching for static assets
- Service workers for offline support
- Skeleton screens for perceived performance
BFF Latency:
- Parallel API calls (CompletableFuture)
- Redis caching for frequently accessed data
- Response shaping (return only needed fields)
- Connection pooling
- gzip/brotli compression
Backend Consistency:
- Strong consistency for financial transactions (Saga, 2PC)
- Eventual consistency for non-critical data (caching, search indexes)
- CQRS for separating read/write models
Balancing Strategy:
- Identify critical paths requiring strong consistency
- Accept eventual consistency for reads (product catalogs, analytics)
- Cache strategically at each layer (CDN → BFF → Service)
- Set SLOs per endpoint
- Monitor with APM and iterate
Example Budget:
- FE Initial Load: < 1.5s (LCP)
- BFF Response: < 200ms (p95)
- BE Service Calls: < 100ms each (parallel)
- Total User Wait: < 2s
Last Updated: January 2026
Duong Ngo
Full-Stack AI Developer with 12+ years of experience