Architecture Overview
Justina is built on a Clean/Hexagonal Architecture pattern, promoting separation of concerns, testability, and maintainability. The system consists of three major components communicating via REST APIs and WebSocket connections.Clean Architecture Principles
The backend follows Robert C. Martinβs Clean Architecture with four distinct layers:Layer Diagram
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRESENTATION LAYER β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββββββ β
β β REST Controllersβ β WebSocket β β Security β β
β β β β Handlers β β Filters β β
β ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ ββββββββββββ¬βββββββββββ β
βββββββββββββΌββββββββββββββββββββββΌββββββββββββββββββββββΌββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Application Services β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β β
β β β AuthService β βSurgeryServiceβ β TelemetryService β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β PORTS (Interfaces) β
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DOMAIN LAYER β
β ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββββ β
β β Core Models β β DTOs β β Domain Logic β β
β β - User β β- AuthResponse β β - Business Rules β β
β β - Surgery β β- TelemetryDTO β β - Validations β β
β β - Movement β β- TrajectoryDTO β β - Calculations β β
β ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Repository Interfaces (Ports) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INFRASTRUCTURE LAYER β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ADAPTERS β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββ β β
β β β JPA/Hibernate β β JWT Security β β WebSocket β β β
β β β PostgreSQL/H2 β β BCrypt Encoder β β STOMP β β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Benefits of This Architecture
Independence
Business logic is independent of frameworks, UI, database, and external agencies
Testability
Business rules can be tested without UI, database, web server, or external elements
UI Independence
UI can change without changing the rest of the system (e.g., swap React for Angular)
Database Independence
Can swap H2 for PostgreSQL without touching business logic
Backend Architecture
Technology Stack
| Component | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Spring Boot | 4.0.2 | Application framework |
| Language | Java | 21 | Primary language |
| Build Tool | Maven | 3.9+ | Dependency management |
| ORM | Hibernate (JPA) | - | Database abstraction |
| Security | Spring Security + JWT | - | Authentication & authorization |
| WebSocket | Spring WebSocket | - | Real-time communication |
| API Docs | SpringDoc OpenAPI | 2.8.6 | API documentation |
| Database (Dev) | H2 | - | In-memory database |
| Database (Prod) | PostgreSQL | 15+ | Production database |
Project Structure
src/main/java/project/Justina/
βββ JustinaApplication.java // Application entry point
β
βββ application/ // APPLICATION LAYER
β βββ ports/ // Port interfaces
β βββ service/ // Business logic services
β βββ AuthService.java // Authentication service
β βββ SurgeryService.java // Surgery management
β
βββ domain/ // DOMAIN LAYER
β βββ model/ // Core business entities
β β βββ User.java // User entity
β β βββ SurgerySession.java // Surgery session
β β βββ Movement.java // Movement point
β β βββ SurgeryEvent.java // Critical events
β βββ dto/ // Data transfer objects
β β βββ LoginRequestDTO.java
β β βββ AuthResponseDTO.java
β β βββ TelemetryDTO.java
β β βββ TrajectoryDTO.java
β βββ repository/ // Repository interfaces
β β βββ UserRepository.java
β β βββ SurgeryRepository.java
β βββ exception/ // Domain exceptions
β βββ AuthException.java
β βββ SurgeryNotFoundException.java
β
βββ infrastructure/ // INFRASTRUCTURE LAYER
βββ adapter/ // Implementation adapters
β βββ entity/ // JPA entities
β βββ mapper/ // Entity-DTO mappers
β βββ repository/ // JPA repository implementations
βββ config/ // Spring configuration
β βββ DataInitializer.java // Initialize default users
β βββ OpenApiConfig.java // Swagger configuration
βββ controller/ // REST endpoints
β βββ AuthController.java // Authentication endpoints
β βββ SurgeryController.java // Surgery endpoints
β βββ GlobalExceptionHandler.java // Error handling
βββ security/ // Security infrastructure
β βββ SecurityConfig.java // Security configuration
β βββ JwtService.java // JWT token management
β βββ JwtAuthenticationFilter.java // JWT validation filter
β βββ ApplicationConfig.java // Bean configurations
βββ websocket/ // WebSocket infrastructure
βββ WebSocketConfig.java // WebSocket configuration
βββ SimulationWebSocketHandler.java // Surgeon connection
βββ AIWebSocketHandler.java // AI service connection
βββ HandshakeInterceptorImpl.java // WebSocket security
Key Design Patterns
1. Dependency Injection
@Service
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
// Constructor injection (Spring auto-wires dependencies)
@Autowired
public AuthService(
UserRepository userRepository,
PasswordEncoder passwordEncoder,
JwtService jwtService
) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.jwtService = jwtService;
}
public AuthResponseDTO login(LoginRequestDTO request) {
// Business logic...
}
}
2. Repository Pattern
// Domain layer - Interface (Port)
public interface UserRepository {
Optional<User> findByUsername(String username);
User save(User user);
}
// Infrastructure layer - Implementation (Adapter)
@Repository
public interface UserJpaRepository
extends JpaRepository<UserEntity, Long>, UserRepository {
// JPA provides implementation automatically
}
3. DTO Pattern
Separate internal domain models from external API contracts:// Domain model (internal)
@Entity
public class User {
private Long id;
private String username;
private String passwordHash;
private String role;
// Internal implementation details
}
// DTO (external API)
public record AuthResponseDTO(
String token,
String username,
String role
) {
// Only expose what clients need
}
Frontend Architecture
Technology Stack
| Component | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Next.js | 16.1.6 | React framework |
| Language | TypeScript | 5.x | Type-safe JavaScript |
| UI Library | React | 19.2.3 | Component library |
| 3D Engine | Babylon.js | 8.51.2 | WebGL 3D rendering |
| State Management | Zustand | 5.0.11 | Global state |
| WebSocket | STOMP.js | 7.3.0 | WebSocket protocol |
| UI Components | Radix UI | - | Accessible components |
| Styling | Tailwind CSS | 4.1.18 | Utility-first CSS |
Application Structure
frontend/
βββ app/
β βββ components/ // React components
β β βββ Scene.tsx // Babylon.js 3D scene
β β βββ UserDropdown.tsx // User menu
β β βββ nav.tsx // Navigation bar
β β βββ dashboard/
β β β βββ ModuleCard.tsx // Dashboard cards
β β βββ ui/ // Reusable UI components
β β βββ button.tsx
β β βββ card.tsx
β β βββ input.tsx
β β
β βββ lib/ // Utility libraries
β β βββ api.ts // REST API client
β β βββ websocketConfig.ts // WebSocket setup
β β βββ enviarTelemetria.ts // Telemetry sender
β β βββ config.ts // Environment config
β β
β βββ store/ // State management
β β βββ surgeryStore.ts // Surgery state (Zustand)
β β
β βββ login/
β β βββ page.tsx // Login page
β β βββ login.actions.ts // Login logic
β β
β βββ dashboard/
β β βββ page.tsx // Dashboard page
β β βββ module.data.ts // Module definitions
β β
β βββ layout.tsx // Root layout
β βββ page.tsx // Home page
β
βββ public/ // Static assets
βββ package.json // Dependencies
βββ tsconfig.json // TypeScript config
3D Simulation with Babylon.js
The surgical simulation is powered by Babylon.js, a powerful WebGL 3D engine.import * as BABYLON from "@babylonjs/core";
import { useEffect, useRef } from "react";
export default function BabylonScene() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const websocketRef = useRef<WebSocket | null>(null);
useEffect(() => {
if (!canvasRef.current) return;
// Create Babylon.js engine
const engine = new BABYLON.Engine(canvasRef.current, true);
const scene = new BABYLON.Scene(engine);
// Setup camera, lights, and 3D objects
const camera = new BABYLON.ArcRotateCamera(
"camera",
Math.PI / 2,
Math.PI / 2,
10,
BABYLON.Vector3.Zero(),
scene
);
camera.attachControl(canvasRef.current, true);
// Load surgical models, instruments, etc.
// ...
// Render loop
engine.runRenderLoop(() => {
scene.render();
});
// Connect WebSocket for telemetry
conectarWebSocket();
return () => {
engine.dispose();
websocketRef.current?.close();
};
}, []);
return <canvas ref={canvasRef} className="w-full h-full" />;
}
WebSocket Integration
Real-time telemetry streaming from the 3D simulation:let websocket: WebSocket | null = null;
export function conectarWebSocket(token: string) {
const WS_URL = process.env.NEXT_PUBLIC_WS_URL;
websocket = new WebSocket(`${WS_URL}/ws/simulation?token=${token}`);
websocket.onopen = () => {
console.log('π WebSocket connected - Simulation started');
// Send START event
enviarTelemetria(0, 0, 0, 'START');
};
websocket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.status === 'SAVED') {
console.log('πΎ Surgery saved:', message.surgeryId);
localStorage.setItem('lastSurgeryId', message.surgeryId);
}
};
websocket.onerror = (error) => {
console.error('β WebSocket error:', error);
};
websocket.onclose = () => {
console.log('π WebSocket disconnected');
};
}
export function enviarTelemetria(
x: number,
y: number,
z: number,
event: string
) {
if (websocket?.readyState === WebSocket.OPEN) {
const telemetria = {
coordinates: { x, y, z },
event: event, // START, MOVE, FINISH
timestamp: new Date().toISOString()
};
websocket.send(JSON.stringify(telemetria));
}
}
State Management
Using Zustand for lightweight, performant state management:import { create } from 'zustand';
interface SurgeryStore {
currentEvent: string;
surgeryId: string | null;
setEvent: (event: string) => void;
setSurgeryId: (id: string) => void;
}
export const useSurgeryStore = create<SurgeryStore>((set) => ({
currentEvent: 'IDLE',
surgeryId: null,
setEvent: (event) => set({ currentEvent: event }),
setSurgeryId: (id) => set({ surgeryId: id })
}));
AI Service Architecture
Technology Stack
| Component | Technology | Purpose |
|---|---|---|
| Language | Python 3.x | Core language |
| HTTP Client | requests | REST API calls |
| Data Processing | pandas, numpy | Trajectory analysis |
| WebSocket | websocket-client | Real-time notifications |
| Web Server | Flask | Health check endpoint |
| Config | python-dotenv | Environment variables |
Pipeline Architecture
The AI service implements a 5-step analysis pipeline for surgical evaluation:def run_pipeline(trajectory_data: Dict) -> Tuple[float, str]:
"""
COMPLETE 5-STEP ANALYSIS PIPELINE
"""
# STEP 1: Data Ingestion & Cleaning
df = _paso1_ingestar_y_limpiar(trajectory_data)
# STEP 2: Dexterity Metrics (Physics-based)
metricas = _paso2_calcular_destreza(df)
# STEP 3: Benchmarking Against Gold Standard
benchmarking = _paso3_benchmarking(df)
# STEP 4: Risk Analysis
riesgo = _paso4_analizar_riesgo(df)
# STEP 5: Intelligent Feedback Generation
score, feedback = _paso5_generar_feedback(metricas, benchmarking, riesgo)
return score, feedback
Step 1: Data Ingestion & Cleaning
def _paso1_ingestar_y_limpiar(trajectory_data: Dict) -> pd.DataFrame:
movements = trajectory_data["movements"]
df = pd.DataFrame([{
"x": m["coordinates"][0],
"y": m["coordinates"][1],
"z": m["coordinates"][2] if len(m["coordinates"]) > 2 else 0,
"event": m["event"],
"timestamp": m["timestamp"]
} for m in movements])
# Sort by timestamp and calculate relative time
df = df.sort_values("timestamp").reset_index(drop=True)
df["t"] = (df["timestamp"] - df["timestamp"].iloc[0]) / 1000.0 # seconds
df["dt"] = df["t"].diff().fillna(0)
return df
Step 2: Dexterity Metrics
Calculates physics-based surgical performance metrics:def _paso2_calcular_destreza(df: pd.DataFrame) -> Dict:
# Calculate position differences
dx = df["x"].diff().fillna(0)
dy = df["y"].diff().fillna(0)
dz = df["z"].diff().fillna(0)
dist = np.sqrt(dx**2 + dy**2 + dz**2)
# Velocity: v = distance / time
v = dist / df["dt"].replace(0, np.inf)
v = v.replace(np.inf, 0)
# Acceleration: a = dv / dt
a = v.diff() / df["dt"].replace(0, np.inf)
a = a.replace(np.inf, 0)
# Jerk: j = da / dt (measures smoothness)
j = a.diff() / df["dt"].replace(0, np.inf)
j = j.replace(np.inf, 0)
# Movement economy: total distance / direct distance
total_dist = dist.sum()
p1 = np.array([df["x"].iloc[0], df["y"].iloc[0], df["z"].iloc[0]])
p2 = np.array([df["x"].iloc[-1], df["y"].iloc[-1], df["z"].iloc[-1]])
direct_dist = np.linalg.norm(p2 - p1)
economia = total_dist / direct_dist if direct_dist > 0 else 1.0
return {
"economia": economia, # Lower is better (ideal < 1.2)
"v_avg": v.mean(), # Average velocity
"a_max": a.abs().max(), # Maximum acceleration
"j_avg": j.abs().mean(), # Average jerk (smoothness)
"total_dist": total_dist,
"duration": df["t"].iloc[-1]
}
Step 3: Benchmarking
Compares trajectory against ideal straight-line path:def _paso3_benchmarking(df: pd.DataFrame) -> Dict:
p_start = np.array([df["x"].iloc[0], df["y"].iloc[0], df["z"].iloc[0]])
p_end = np.array([df["x"].iloc[-1], df["y"].iloc[-1], df["z"].iloc[-1]])
def dist_to_line(p, a, b):
"""Calculate perpendicular distance from point to line"""
if np.all(a == b): return np.linalg.norm(p - a)
return np.linalg.norm(np.cross(b - a, a - p)) / np.linalg.norm(b - a)
# Calculate deviation for each point
desviaciones = [
dist_to_line(np.array([r.x, r.y, r.z]), p_start, p_end)
for r in df.itertuples()
]
return {
"desviacion_avg": np.mean(desviaciones),
"precision": max(0, 100 - np.mean(desviaciones) * 10) # 0-100 scale
}
Step 4: Risk Analysis
def _paso4_analizar_riesgo(df: pd.DataFrame) -> Dict:
# Count critical events
tumor_touches = (df["event"] == "TUMOR_TOUCH").sum()
hemorrhages = (df["event"] == "HEMORRHAGE").sum()
# Identify problem quadrants
mid_x = (df["x"].max() + df["x"].min()) / 2
mid_y = (df["y"].max() + df["y"].min()) / 2
problemas = df[df["event"].isin(["TUMOR_TOUCH", "HEMORRHAGE"])]
cuadrantes_criticos = []
if not problemas.empty:
for p in problemas.itertuples():
pos = ("Sup" if p.y > mid_y else "Inf") + \
("-Der" if p.x > mid_x else "-Izq")
if pos not in cuadrantes_criticos:
cuadrantes_criticos.append(pos)
return {
"touches": int(tumor_touches),
"hemorrhages": int(hemorrhages),
"cuadrantes": cuadrantes_criticos
}
Step 5: Feedback Generation
def _paso5_generar_feedback(m: Dict, b: Dict, r: Dict) -> Tuple[float, str]:
# Calculate final score
score = 100.0
score -= r["touches"] * 8 # -8 per tumor contact
score -= r["hemorrhages"] * 15 # -15 per hemorrhage
if m["economia"] > 1.5: score -= 10 # -10 for inefficient movement
score = max(0, min(100, score))
# Generate feedback markdown
status = (
"π EXCELENTE" if score >= 90 else
"β
BUENO" if score >= 75 else
"β οΈ MEJORABLE" if score >= 60 else
"β DEFICIENTE"
)
feedback = f"""
### {status} - Score: {score:.1f}/100
#### π¨ CRITICAL ALERTS
- Hemorrhages: {r["hemorrhages"]} {"(REVIEW TECHNIQUE)" if r["hemorrhages"] > 0 else "(None)"}
- Tumor Contacts: {r["touches"]}
- Risk Quadrants: {", ".join(r["cuadrantes"]) if r["cuadrantes"] else "None"}
#### π DEXTERITY METRICS
- **Movement Economy:** {m["economia"]:.2f}x (Ideal < 1.2x)
- **Smoothness (Avg Jerk):** {m["j_avg"]:.2f}
- **Precision vs Gold Standard:** {b["precision"]:.1f}%
#### π STATISTICS
- **Total Duration:** {m["duration"]:.1f}s
- **Distance Traveled:** {m["total_dist"]:.2f} units
- **Average Velocity:** {m["v_avg"]:.2f} u/s
#### π‘ RECOMMENDATIONS
"""
# Add specific recommendations
if r["hemorrhages"] > 0:
feedback += "- Prioritize vascular control in critical quadrants.\n"
if m["economia"] > 1.8:
feedback += "- Plan more direct trajectories to reduce fatigue.\n"
if b["precision"] < 70:
feedback += "- Maintain better stability in executing ideal path.\n"
if score < 80:
feedback += "- Increase simulator practice to improve motor coordination.\n"
return score, feedback.strip()
WebSocket Client
The AI service listens for new surgery notifications:class AIWebSocketClient:
def on_message(self, ws, message):
data = json.loads(message)
# Check if it's a new surgery notification
if data.get("event") == "NEW_SURGERY":
surgery_id = data.get("surgeryId")
print(f"π New surgery detected: {surgery_id}")
# Process in separate thread
thread = threading.Thread(
target=self.procesar_cirugia_async,
args=(surgery_id,)
)
thread.daemon = True
thread.start()
def procesar_cirugia_async(self, surgery_id):
# 1. Fetch trajectory data
trajectory_data = self.client.get_trajectory(surgery_id)
# 2. Analyze with pipeline
score, feedback = run_pipeline(trajectory_data)
# 3. Send results back
self.client.send_analysis(surgery_id, score, feedback)
Communication Protocols
REST API Endpoints
Authentication
POST /api/v1/auth/login
POST /api/v1/auth/register
GET /api/v1/auth/me
Surgery Management
GET /api/v1/surgeries/{id}/trajectory # Get movement data
POST /api/v1/surgeries/{id}/analysis # Submit AI analysis
WebSocket Endpoints
Simulation Stream (Surgeons)
ws://backend:8080/ws/simulation?token=<JWT>
{
"coordinates": {"x": 10.5, "y": 20.3, "z": 15.7},
"event": "MOVE",
"timestamp": "2024-01-15T10:30:00Z"
}
{
"status": "SAVED",
"surgeryId": "550e8400-e29b-41d4-a716-446655440000"
}
AI Notification Channel
ws://backend:8080/ws/ai?token=<JWT>
{
"event": "NEW_SURGERY",
"surgeryId": "550e8400-e29b-41d4-a716-446655440000"
}
Security Architecture
JWT Authentication Flow
Role-Based Access Control
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
// Public endpoints
.requestMatchers("/api/v1/auth/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
// Surgeon-only endpoints
.requestMatchers("/api/v1/surgeries/*/trajectory")
.hasAnyRole("SURGEON", "IA")
// AI-only endpoints
.requestMatchers("/api/v1/surgeries/*/analysis")
.hasRole("IA")
// All other endpoints require authentication
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Database Schema
Entity Relationship Diagram
Example Queries
// Fetch surgery with all movements
SurgerySession surgery = surgeryRepository.findById(surgeryId)
.orElseThrow(() -> new SurgeryNotFoundException(surgeryId));
List<Movement> movements = surgery.getMovements(); // Lazy loaded
// Get user's surgeries
List<SurgerySession> surgeries = surgeryRepository
.findByUserId(userId);
// Get surgeries by status
List<SurgerySession> pending = surgeryRepository
.findByStatus(SurgeryStatus.PENDING_ANALYSIS);
Performance Considerations
Backend Optimizations
- Connection Pooling: HikariCP for efficient database connections
- Lazy Loading: JPA relationships loaded on-demand
- Caching: Spring Cache for frequently accessed data
- Async Processing: AI analysis runs in separate threads
Frontend Optimizations
- Code Splitting: Next.js automatic route-based splitting
- Tree Shaking: Unused code eliminated in production builds
- WebGL Optimization: Babylon.js scene optimization
- State Minimization: Zustand for lightweight state management
WebSocket Optimizations
- Binary Frames: Efficient data transfer format
- Compression: WebSocket compression extension
- Heartbeat: Keep-alive pings to maintain connection
- Reconnection: Automatic reconnection on disconnect
Deployment Architecture
Development
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Frontend ββββββΆβ Backend ββββββΆβ H2 Memory β
β :3000 β β :8080 β β Database β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β
βΌ
βββββββββββββββ
β AI Service β
β Python β
βββββββββββββββ
Production (Docker)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Compose β
β β
β βββββββββββββ βββββββββββββ βββββββββββββββββ β
β β Frontend βββββΆβ Backend βββββΆβ PostgreSQL β β
β β Next.js β βSpring Bootβ β Database β β
β β :3000 β β :8080 β β :5432 β β
β βββββββββββββ βββββββββββββ βββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββ β
β βAI Service β β
β β Python β β
β βββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Next Steps
API Reference
Complete endpoint documentation with examples
Deployment Guide
Deploy Justina to production environments
Features
Explore platform capabilities
User Guide
Learn how to use the platform