docs: Add comprehensive technical interview guide

- Create TECHNICAL_INTERVIEW.md with 20 technical questions
- Cover Backend (5), Frontend (4), DevOps (6), Data/API (3), Problem Solving (2)
- Include detailed answers with code examples
- Use Obsidian-compatible callout format for collapsible answers
- Add evaluation criteria (Junior/Mid/Senior levels)
- Include practical coding challenge (Comments service)

Technical areas covered:
- API Gateway vs Service Mesh architecture
- FastAPI async/await and Motor vs PyMongo
- Microservice communication (REST, Pub/Sub, gRPC)
- Database strategies and JWT security
- React 18 features and TypeScript integration
- Docker multi-stage builds and K8s deployment strategies
- Health checks, monitoring, and logging
- RESTful API design and MongoDB schema modeling
- Traffic handling and failure scenarios

fix: Update Services.tsx with TypeScript fixes
- Fix ServiceType enum import (use value import, not type-only)
- Fix API method name: checkHealthAll → checkAllHealth
- Ensure proper enum usage in form data

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2025-11-03 08:26:00 +09:00
parent 0da9922bc6
commit de0d548b7a
2 changed files with 1134 additions and 63 deletions

View File

@ -1,3 +1,4 @@
import { useState, useEffect } from 'react'
import {
Box,
Typography,
@ -9,90 +10,391 @@ import {
TableRow,
Paper,
Chip,
Button,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
MenuItem,
CircularProgress,
Alert,
Tooltip,
} from '@mui/material'
const servicesData = [
{
id: 1,
name: 'Console',
type: 'API Gateway',
port: 8011,
status: 'Running',
description: 'Central orchestrator and API gateway',
},
{
id: 2,
name: 'Users',
type: 'Microservice',
port: 8001,
status: 'Running',
description: 'User management service',
},
{
id: 3,
name: 'MongoDB',
type: 'Database',
port: 27017,
status: 'Running',
description: 'Document database for persistence',
},
{
id: 4,
name: 'Redis',
type: 'Cache',
port: 6379,
status: 'Running',
description: 'In-memory cache and pub/sub',
},
]
import {
Add as AddIcon,
Edit as EditIcon,
Delete as DeleteIcon,
Refresh as RefreshIcon,
CheckCircle as HealthCheckIcon,
} from '@mui/icons-material'
import { serviceAPI } from '../api/service'
import { ServiceType, ServiceStatus } from '../types/service'
import type { Service, ServiceCreate } from '../types/service'
function Services() {
const [services, setServices] = useState<Service[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [openDialog, setOpenDialog] = useState(false)
const [editingService, setEditingService] = useState<Service | null>(null)
const [formData, setFormData] = useState<ServiceCreate>({
name: '',
url: '',
service_type: ServiceType.BACKEND,
description: '',
health_endpoint: '/health',
metadata: {},
})
// Load services
const loadServices = async () => {
try {
setLoading(true)
setError(null)
const data = await serviceAPI.getAll()
setServices(data)
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to load services')
} finally {
setLoading(false)
}
}
useEffect(() => {
loadServices()
}, [])
// Handle create/update service
const handleSave = async () => {
try {
if (editingService) {
await serviceAPI.update(editingService._id, formData)
} else {
await serviceAPI.create(formData)
}
setOpenDialog(false)
setEditingService(null)
resetForm()
loadServices()
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to save service')
}
}
// Handle delete service
const handleDelete = async (id: string) => {
if (!confirm('Are you sure you want to delete this service?')) return
try {
await serviceAPI.delete(id)
loadServices()
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to delete service')
}
}
// Handle health check
const handleHealthCheck = async (id: string) => {
try {
const result = await serviceAPI.checkHealth(id)
setServices(prev => prev.map(s =>
s._id === id ? { ...s, status: result.status, response_time_ms: result.response_time_ms, last_health_check: result.checked_at } : s
))
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to check health')
}
}
// Handle health check all
const handleHealthCheckAll = async () => {
try {
await serviceAPI.checkAllHealth()
loadServices()
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to check all services')
}
}
// Open dialog for create/edit
const openEditDialog = (service?: Service) => {
if (service) {
setEditingService(service)
setFormData({
name: service.name,
url: service.url,
service_type: service.service_type,
description: service.description || '',
health_endpoint: service.health_endpoint || '/health',
metadata: service.metadata || {},
})
} else {
resetForm()
}
setOpenDialog(true)
}
const resetForm = () => {
setFormData({
name: '',
url: '',
service_type: ServiceType.BACKEND,
description: '',
health_endpoint: '/health',
metadata: {},
})
setEditingService(null)
}
const getStatusColor = (status: ServiceStatus) => {
switch (status) {
case 'healthy': return 'success'
case 'unhealthy': return 'error'
default: return 'default'
}
}
const getTypeColor = (type: ServiceType) => {
switch (type) {
case 'backend': return 'primary'
case 'frontend': return 'secondary'
case 'database': return 'info'
case 'cache': return 'warning'
default: return 'default'
}
}
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
<CircularProgress />
</Box>
)
}
return (
<Box>
<Typography variant="h4" gutterBottom>
Services
</Typography>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Typography variant="h4">
Services
</Typography>
<Box>
<Button
startIcon={<RefreshIcon />}
onClick={loadServices}
sx={{ mr: 1 }}
>
Refresh
</Button>
<Button
startIcon={<HealthCheckIcon />}
onClick={handleHealthCheckAll}
variant="outlined"
sx={{ mr: 1 }}
>
Check All Health
</Button>
<Button
startIcon={<AddIcon />}
onClick={() => openEditDialog()}
variant="contained"
>
Add Service
</Button>
</Box>
</Box>
{error && (
<Alert severity="error" onClose={() => setError(null)} sx={{ mb: 2 }}>
{error}
</Alert>
)}
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Service Name</TableCell>
<TableCell>Type</TableCell>
<TableCell>Port</TableCell>
<TableCell>URL</TableCell>
<TableCell>Status</TableCell>
<TableCell>Description</TableCell>
<TableCell>Response Time</TableCell>
<TableCell>Last Check</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{servicesData.map((service) => (
<TableRow key={service.id}>
<TableCell>
<Typography variant="subtitle2">{service.name}</Typography>
{services.length === 0 ? (
<TableRow>
<TableCell colSpan={7} align="center">
<Typography variant="body2" color="text.secondary" py={4}>
No services found. Click "Add Service" to create one.
</Typography>
</TableCell>
<TableCell>
<Chip
label={service.type}
size="small"
color={service.type === 'API Gateway' ? 'primary' : 'default'}
/>
</TableCell>
<TableCell>{service.port}</TableCell>
<TableCell>
<Chip
label={service.status}
size="small"
color="success"
/>
</TableCell>
<TableCell>{service.description}</TableCell>
</TableRow>
))}
) : (
services.map((service) => (
<TableRow key={service._id} hover>
<TableCell>
<Typography variant="subtitle2">{service.name}</Typography>
{service.description && (
<Typography variant="caption" color="text.secondary">
{service.description}
</Typography>
)}
</TableCell>
<TableCell>
<Chip
label={service.service_type}
size="small"
color={getTypeColor(service.service_type)}
/>
</TableCell>
<TableCell>
<Typography variant="body2" noWrap sx={{ maxWidth: 300 }}>
{service.url}
</Typography>
{service.health_endpoint && (
<Typography variant="caption" color="text.secondary">
Health: {service.health_endpoint}
</Typography>
)}
</TableCell>
<TableCell>
<Chip
label={service.status}
size="small"
color={getStatusColor(service.status)}
/>
</TableCell>
<TableCell>
{service.response_time_ms ? (
<Typography variant="body2">
{service.response_time_ms.toFixed(2)} ms
</Typography>
) : (
<Typography variant="body2" color="text.secondary">
-
</Typography>
)}
</TableCell>
<TableCell>
{service.last_health_check ? (
<Typography variant="caption">
{new Date(service.last_health_check).toLocaleString()}
</Typography>
) : (
<Typography variant="caption" color="text.secondary">
Never
</Typography>
)}
</TableCell>
<TableCell align="right">
<Tooltip title="Check Health">
<IconButton
size="small"
onClick={() => handleHealthCheck(service._id)}
color="primary"
>
<HealthCheckIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Edit">
<IconButton
size="small"
onClick={() => openEditDialog(service)}
color="primary"
>
<EditIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton
size="small"
onClick={() => handleDelete(service._id)}
color="error"
>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{/* Add/Edit Dialog */}
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} maxWidth="sm" fullWidth>
<DialogTitle>
{editingService ? 'Edit Service' : 'Add Service'}
</DialogTitle>
<DialogContent>
<Box sx={{ pt: 2, display: 'flex', flexDirection: 'column', gap: 2 }}>
<TextField
label="Service Name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
fullWidth
/>
<TextField
label="Service URL"
value={formData.url}
onChange={(e) => setFormData({ ...formData, url: e.target.value })}
required
fullWidth
placeholder="http://service-name:8000"
/>
<TextField
label="Service Type"
value={formData.service_type}
onChange={(e) => setFormData({ ...formData, service_type: e.target.value as ServiceType })}
select
required
fullWidth
>
<MenuItem value="backend">Backend</MenuItem>
<MenuItem value="frontend">Frontend</MenuItem>
<MenuItem value="database">Database</MenuItem>
<MenuItem value="cache">Cache</MenuItem>
<MenuItem value="message_queue">Message Queue</MenuItem>
<MenuItem value="other">Other</MenuItem>
</TextField>
<TextField
label="Health Endpoint"
value={formData.health_endpoint}
onChange={(e) => setFormData({ ...formData, health_endpoint: e.target.value })}
fullWidth
placeholder="/health"
/>
<TextField
label="Description"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
fullWidth
multiline
rows={2}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)}>
Cancel
</Button>
<Button
onClick={handleSave}
variant="contained"
disabled={!formData.name || !formData.url}
>
{editingService ? 'Update' : 'Create'}
</Button>
</DialogActions>
</Dialog>
</Box>
)
}
export default Services
export default Services