Project Name: Watt Tracker Microservices
Domain: Smart Energy Management & IoT
Architecture: Event-Driven Microservices
Services: 9 microservices (6 business + 3 infrastructure)
Status: ✅ Fully Operational on Localhost
Answer: Watt Tracker is an IoT-based smart energy management platform built using microservices architecture. It tracks energy consumption from smart devices, provides real-time analytics, sends threshold-based alerts, and offers AI-powered energy-saving insights. It solves the problem of unmonitored energy consumption by providing visibility into usage patterns, proactive alerting when thresholds are exceeded, and actionable recommendations to reduce energy costs.
Answer:
┌─────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ React Frontend (Port 5173) │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ API GATEWAY │
│ Spring Cloud Gateway (Port 9090) │
│ (Routing, Load Balancing, Auth, CORS) │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ SERVICE DISCOVERY & CONFIG │
│ Eureka Server (8761) │ Config Server (8888) │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌───▼────┐ ┌───────▼──────┐ ┌──────▼─────┐
│ INGEST │ │ USAGE │ │ ALERT │
│(8082) │───►│ (8083) │───►│ (8084) │
└────────┘ └──────────────┘ └────────────┘
│ │ │
│ ┌──────▼──────┐ │
│ │ INFLUXDB │ │
│ └─────────────┘ │
│ │
┌────▼────────────────────────────────────▼────┐
│ KAFKA MESSAGE BUS │
│ Topics: energy-usage, energy-alerts │
└──────────────────────────────────────────────┘
Answer: There are 9 microservices:
Infrastructure (3):
- Eureka Service (Port 8761) - Service Discovery
- Config Service (Port 8888) - Centralized Configuration
- API Gateway (Port 9090) - Request Routing
Business Services (6): 4. User Service (Port 8086) - User management & authentication 5. Device Service (Port 8081) - Smart device management 6. Ingestion Service (Port 8082) - Energy data ingestion 7. Usage Service (Port 8083) - Analytics & time-series data 8. Alert Service (Port 8084) - Alert management & notifications 9. Insight Service (Port 8085) - AI-powered energy insights
Answer:
- Backend: Spring Boot 4.0.1, Java 21, Spring Cloud 2025.1.0
- Frontend: React 19.2.0, TypeScript, Tailwind CSS, Recharts
- Databases: MySQL 8.3.0 (relational), InfluxDB 2.7 (time-series)
- Messaging: Apache Kafka (KRaft mode)
- Service Discovery: Netflix Eureka
- API Gateway: Spring Cloud Gateway
- Security: JWT (jjwt 0.12.3), Spring Security, BCrypt
- AI: Spring AI 1.1.2, Ollama (llama3.2)
- Build Tool: Maven
- Infrastructure: Docker Compose
Answer:
- Scalability: Each service can scale independently based on load (e.g., Ingestion Service needs higher throughput than Alert Service)
- Technology Diversity: Used InfluxDB for time-series data while keeping MySQL for relational data
- Fault Isolation: If the Insight Service fails, other services continue operating
- Independent Deployment: Can deploy updates to User Service without affecting Device Service
- Team Autonomy: Different teams can work on different services independently
- Domain-Driven Design: Each service owns its domain (users, devices, usage, alerts)
Answer:
- API Gateway Pattern - Single entry point for all client requests
- Service Discovery Pattern - Eureka for dynamic service registration
- Externalized Configuration - Spring Cloud Config Server
- Event-Driven Architecture - Kafka for async communication
- CQRS (Command Query Responsibility Segregation) - Write to Kafka, read from InfluxDB
- Database per Service - Each microservice owns its data
- Circuit Breaker - Try-catch with fallback responses
- Saga Pattern - Distributed transaction handling via events
Answer: Synchronous (REST APIs):
- Device Service calls User Service to validate user existence
- Usage Service calls Device Service to get device details
- Insight Service calls Usage Service via WebClient
Asynchronous (Kafka Events):
- Ingestion Service → Usage Service (energy-usage topic)
- Usage Service → Alert Service (energy-alerts topic)
Example Code (WebClient):
public Flux<String> getSavingTips(Long userId) {
return usageWebClient.get()
.uri("/api/v1/usage/{userId}?days=7", userId)
.retrieve()
.bodyToFlux(EnergyUsageDTO.class)
.flatMap(usage -> generateTips(usage))
.onErrorResume(e -> Flux.just("Unable to generate tips"));
}Answer:
1. Device sends data → POST /api/v1/ingestion
2. Ingestion Service validates and publishes EnergyUsageEvent to Kafka (energy-usage topic)
3. Usage Service consumes the event from Kafka
4. Usage Service stores time-series data in InfluxDB
5. Scheduled job (every 10 seconds) checks if usage exceeds threshold
6. If threshold exceeded → publishes AlertingEvent to Kafka (energy-alerts topic)
7. Alert Service consumes alert event
8. Alert Service saves alert to MySQL and sends email notification
Answer: Polyglot Persistence - choosing the right database for each use case:
MySQL (Relational):
- User Service: User profiles, authentication data
- Device Service: Device metadata, user-device relationships
- Alert Service: Alert records, acknowledgment status
- Why: ACID compliance, complex queries, relationships
InfluxDB (Time-Series):
- Usage Service: Energy consumption data points
- Why: Optimized for high-write throughput, time-based queries, aggregation
- Example: Querying energy usage over last 30 days with 1-hour intervals
Answer: Routes are configured in api-gateway.properties:
spring:
cloud:
gateway:
routes:
- id: device-service
uri: lb://DEVICE-SERVICE
predicates:
- Path=/device-service/**
filters:
- StripPrefix=1
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/**
filters:
- StripPrefix=1Key Features:
- Path-based routing
- Load balancing with Eureka (lb://)
- CORS configuration for frontend
- JWT validation
- StripPrefix filter removes service name from path
Answer: Eureka provides Service Discovery:
- Services register themselves on startup
- Clients fetch registry to discover service locations
- Health checks every 30 seconds
- Automatic deregistration of unhealthy instances
Status: 6 services currently registered and UP
- ALERT-SERVICE, API-GATEWAY, DEVICE-SERVICE, INGESTION-SERVICE, USAGE-SERVICE, USER-SERVICE
Answer:
- Config Server (Port 8888) serves configurations from Git repository
- Config Repo:
config-repo/configs/directory - Profile-specific configs: Each service has its own
.propertiesfile - Shared config:
application.propertiescontains common settings (DB, Kafka, JWT)
Benefits:
- Environment-specific configurations without code changes
- Centralized management
- Version control for configurations
- Hot reload without restart
Answer: Command Query Responsibility Segregation separates read and write operations.
Implementation:
- Write Path: Ingestion Service → Kafka (energy-usage topic)
- Read Path: Usage Service → InfluxDB queries
Why CQRS:
- InfluxDB optimized for writes (thousands of data points/minute)
- Separate read models for different query patterns
- Time-series aggregation doesn't need transactional consistency
Answer:
- Frontend: React 19.2.0 with TypeScript
- Port: 5173 (Vite dev server)
- API Calls: Axios to API Gateway (Port 9090)
- CORS: Configured in API Gateway to allow localhost:5173
- Authentication: JWT tokens stored in localStorage
- Charts: Recharts for energy usage visualization
- Styling: Tailwind CSS
Answer:
# Infrastructure Stack (docker-compose.yml)
mysql: Port 3306 - User/Device/Alert data
kafka: Port 9092/9094 - Event streaming
kafka-ui: Port 8070 - Kafka management UI
influxdb: Port 8072 - Time-series data
mailpit: Port 8025 - Email testing UIAll services are currently running and healthy.
Answer: JWT-based stateless authentication:
Login Flow:
- User sends credentials to
/api/v1/user/login - User Service validates credentials (BCrypt password matching)
- Generates JWT token with user ID and role
- Token returned to client
- Client includes token in Authorization header for subsequent requests
Token Configuration:
- Secret: 256-bit key
- Expiration: 24 hours (86400000 ms)
- Algorithm: HS256
Code Example:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getId().toString())
.claim("role", user.getRole())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS256)
.compact();
}
}Answer:
- BCryptPasswordEncoder with strength 10
- Passwords are hashed before storage
- Never store or return plain passwords in API responses
- Salt is automatically generated by BCrypt
Answer:
- API Gateway receives request with Authorization header
- Extracts JWT token from header
- Validates token signature using shared secret
- Extracts user ID and role from claims
- Adds user info to request headers for downstream services
- Routes request to appropriate service
Answer:
- Internal Services: No authentication (assumed secure network)
- API Gateway: Validates JWT before routing
- User Context: User ID passed in headers after Gateway validation
- Future: Could implement mTLS for service-to-service authentication
Answer: Public (No JWT required):
- POST
/api/v1/user/register- User registration - POST
/api/v1/user/login- User login
Protected (JWT required):
- All other endpoints
- GET
/api/v1/device/** - POST
/api/v1/ingestion/** - GET
/api/v1/usage/**
Answer: Configured in API Gateway:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "http://localhost:5173"
allowedMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allowedHeaders: ["*"]
allowCredentials: trueAnswer:
- Content-Security-Policy
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- X-XSS-Protection: 1; mode=block
Answer:
- Use Spring Data JPA with parameterized queries
- Never concatenate user input into SQL strings
- Example safe query:
@Query("SELECT d FROM Device d WHERE d.userId = :userId")
List<Device> findByUserId(@Param("userId") Long userId);Answer:
- Configures security filter chain
- JWT validation filter
- Password encoder configuration
- Endpoint access control
- Exception handling for authentication failures
Answer:
- Stateless - No server-side sessions
- JWT tokens contain all necessary user info
- Token expiration enforced (24 hours)
- Refresh token pattern not implemented (could be added)
Answer:
- High Throughput: Handles 10,000+ messages/minute
- Durability: Messages persisted to disk
- Scalability: Horizontal scaling with partitions
- Fault Tolerance: Replication across brokers
- Replayability: Can reprocess historical events
- No Zookeeper: Using KRaft mode (simpler architecture)
Answer:
topics:
energy-usage:
partitions: 3
replication: 1
producer: ingestion-service
consumer: usage-service
energy-alerts:
partitions: 3
replication: 1
producer: usage-service
consumer: alert-serviceAnswer:
@Configuration
public class KafkaProducerConfig {
@Bean
public ProducerFactory<String, EnergyUsageEvent> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9094");
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return new DefaultKafkaProducerFactory<>(config);
}
@Bean
public KafkaTemplate<String, EnergyUsageEvent> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}Answer:
@Configuration
public class KafkaConsumerConfig {
@Bean
public ConsumerFactory<String, EnergyUsageEvent> consumerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9094");
config.put(ConsumerConfig.GROUP_ID_CONFIG, "usage-service-group");
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
config.put(JsonDeserializer.TRUSTED_PACKAGES, "*");
return new DefaultKafkaConsumerFactory<>(config);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, EnergyUsageEvent> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, EnergyUsageEvent> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}Answer:
- Same Partition: Messages with same key go to same partition
- EnergyUsageEvent: Uses
deviceIdas key - This ensures all events for a specific device are processed in order
Answer:
public class EnergyUsageEvent {
private Long deviceId;
private Long userId;
private Double energyConsumed;
private LocalDateTime timestamp;
}
public class AlertingEvent {
private Long userId;
private Long deviceId;
private Double currentUsage;
private Double threshold;
private String alertType;
private LocalDateTime timestamp;
}Answer:
- Ack Mode: Manual acknowledgment after successful processing
- Retry: 3 attempts with exponential backoff
- Dead Letter Queue: Failed messages routed to DLT topic
- Idempotency: Consumer checks if already processed
Answer:
- Target: 10,000 events per minute
- Threads: 2 parallel threads
- Rate: ~167 events per second
- Simulation: Built-in data generator for testing
Answer:
- Kafka UI (Port 8070): Web-based management
- Metrics: Message rates, lag, consumer offsets
- Topics: Can view messages, partitions, consumer groups
- Alerts: Can set up alerts for consumer lag
Answer:
- Consumer Lag: Difference between produced and consumed messages
- Monitoring: Kafka UI shows lag per partition
- Scaling: Add more consumer instances to consumer group
- Optimization: Tune batch size and poll intervals
Answer:
- Decoupling: Services don't need to know about each other
- Resilience: If Alert Service is down, events are queued
- Scalability: Can add more consumers without changing producers
- Audit Trail: Complete history of events
- Async Processing: Non-blocking operations
Answer:
- Idempotent Consumers: Check if event already processed
- Database Constraints: Unique constraints on event IDs
- Manual Ack: Acknowledge only after successful processing
- Transactions: Use Kafka transactions for producer (not implemented)
Answer:
- Producer: Blocks until timeout, then throws exception
- Fallback: Could implement fallback to direct REST calls
- Retry: Exponential backoff retry
- Alert: Monitoring alerts when Kafka unavailable
Answer:
- Backward Compatibility: New fields are optional
- Versioning: Event schema versions in event metadata
- JSON: Flexible format allows field additions
- Future: Could use Avro with Schema Registry
Answer:
- Default: 1 MB per message
- Configuration:
max.request.sizein producer - Best Practice: Keep events small (< 100 KB)
- Large Messages: Use reference to external storage (S3)
Answer:
- Optimized for Writes: Handles high ingestion rates
- Time-Based Queries: Efficient range queries
- Retention Policies: Automatic data expiration
- Aggregation: Built-in functions for downsampling
- Compression: Efficient storage of time-series data
Answer:
public List<EnergyUsage> getUsageForUser(Long userId, int days) {
String query = String.format(
"from(bucket: \"energy_bucket\")" +
" |> range(start: -%dd)" +
" |> filter(fn: (r) => r._measurement == \"energy_usage\")" +
" |> filter(fn: (r) => r.userId == \"%s\")" +
" |> aggregateWindow(every: 1h, fn: mean)",
days, userId
);
QueryApi queryApi = influxDBClient.getQueryApi();
return queryApi.query(query, EnergyUsage.class);
}Answer:
- Raw Data: 30 days
- Hourly Aggregates: 1 year
- Daily Aggregates: Indefinite
- Configured in InfluxDB bucket settings
Answer:
- Flyway: Database migration tool
- Location:
src/main/resources/db/migration/ - Naming:
V1__create_users_table.sql - Automatic: Runs on application startup
- Versioning: Tracks schema versions
Answer:
-- V1__create_users_table.sql
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) DEFAULT 'USER',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- V2__create_devices_table.sql
CREATE TABLE devices (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
type VARCHAR(50) NOT NULL,
location VARCHAR(100),
user_id BIGINT,
FOREIGN KEY (user_id) REFERENCES users(id)
);Answer:
- Indexes: Created on frequently queried columns (user_id, device_id)
- Pagination: Use Pageable for large result sets
- Lazy Loading: @ManyToOne fetch type LAZY
- Query Optimization: Use @Query for complex queries
- Connection Pooling: HikariCP with 10 connections
Answer:
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
surname VARCHAR(100),
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
address VARCHAR(255),
alerting BOOLEAN DEFAULT FALSE,
energy_alerting_threshold DOUBLE DEFAULT 100.0,
email_notifications BOOLEAN DEFAULT FALSE,
role VARCHAR(50) DEFAULT 'USER'
);Answer:
@Service
@Transactional
public class UserService {
@Transactional(readOnly = true)
public User getUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
@Transactional
public User createUser(UserDTO dto) {
User user = new User();
user.setName(dto.getName());
user.setEmail(dto.getEmail());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
return userRepository.save(user);
}
}Answer:
CREATE TABLE devices (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
type ENUM('LIGHTING', 'HVAC', 'APPLIANCE', 'ELECTRONICS', 'OTHER'),
location VARCHAR(100),
user_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);Answer:
- MySQL:
mysqldumpfor logical backups - InfluxDB: Built-in backup/restore commands
- Schedule: Daily automated backups
- Retention: Keep backups for 30 days
- Storage: Backups stored on separate disk
Answer:
CREATE TABLE alerts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
device_id BIGINT,
type VARCHAR(50) NOT NULL,
severity VARCHAR(20) NOT NULL,
message TEXT,
value DOUBLE,
threshold DOUBLE,
timestamp TIMESTAMP NOT NULL,
acknowledged BOOLEAN DEFAULT FALSE,
email_sent BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);Answer:
- Retry: Spring Retry with exponential backoff
- Circuit Breaker: Fail fast if DB is down
- Health Checks: Actuator health endpoint
- Monitoring: Alerts when connection pool exhausted
- Fallback: Return cached data if available
Answer: Spring Data JPA with Hibernate:
- Simplified CRUD operations
- Derived query methods
- Pagination support
- Custom queries with @Query
- Entity relationships mapping
Answer:
// Pagination
@GetMapping("/users")
public Page<User> getUsers(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userRepository.findAll(PageRequest.of(page, size));
}
// Streaming
@QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE))
@Query("SELECT u FROM User u")
Stream<User> streamAllUsers();Answer:
Measurement: energy_usage
Tags:
- deviceId (indexed)
- userId (indexed)
Fields:
- energyConsumed (float)
Timestamp:
- event timestamp (nanosecond precision)
Answer:
- Receives request for energy-saving tips
- Fetches user's energy usage from Usage Service (WebClient)
- Constructs prompt with usage data
- Calls Ollama LLM API
- Streams response back to client (SSE)
- Provides fallback if AI service unavailable
Answer:
- Framework: Spring AI 1.1.2
- Provider: Ollama (local LLM)
- Model: llama3.2:latest
- Advantages: No API costs, data privacy, low latency
- Deployment: Runs locally on developer machine
Answer:
@Service
public class InsightService {
private final ChatClient chatClient;
private final WebClient usageWebClient;
public InsightService(ChatClient.Builder chatClientBuilder,
@Value("${usage.service.url}") String usageUrl) {
this.chatClient = chatClientBuilder.build();
this.usageWebClient = WebClient.builder()
.baseUrl(usageUrl)
.build();
}
public Flux<String> getSavingTips(Long userId) {
return usageWebClient.get()
.uri("/api/v1/usage/{userId}?days=7", userId)
.retrieve()
.bodyToFlux(EnergyUsageDTO.class)
.collectList()
.flatMapMany(usages -> {
String prompt = buildPrompt(usages);
return chatClient.prompt()
.user(prompt)
.stream()
.content();
})
.onErrorResume(e -> Flux.just("Unable to generate tips at this time"));
}
private String buildPrompt(List<EnergyUsageDTO> usages) {
return "Based on this energy usage data: " + usages +
", provide 3 specific energy-saving recommendations.";
}
}Answer:
- SSE: HTTP-based streaming protocol
- Why: Real-time streaming of AI-generated text
- Benefits:
- Lower overhead than WebSockets
- Automatic reconnection
- Works over HTTP
- Perfect for streaming LLM responses
Answer:
.onErrorResume(e -> {
log.error("AI service error", e);
return Flux.just("AI service temporarily unavailable. " +
"Here are general tips: 1. Turn off unused devices...");
})Answer:
- Energy-Saving Tips: Device-specific recommendations
- Usage Patterns: Peak hours identification
- Cost Reduction: Money-saving strategies
- Efficiency Scores: Device performance analysis
- Comparative Analysis: Usage vs. similar households
Answer:
private String constructPrompt(List<EnergyUsage> usages) {
StringBuilder sb = new StringBuilder();
sb.append("You are an energy efficiency expert. ");
sb.append("Analyze the following energy usage data:\n\n");
for (EnergyUsage usage : usages) {
sb.append(String.format("Device: %s, Usage: %.2f kWh, Time: %s\n",
usage.getDeviceName(),
usage.getEnergyConsumed(),
usage.getTimestamp()));
}
sb.append("\nProvide 3 actionable energy-saving tips ");
sb.append("specific to this usage pattern.");
return sb.toString();
}Answer:
- Local LLM: Ollama runs locally (no per-token costs)
- Caching: Cache frequent insights
- Rate Limiting: Limit requests per user per hour
- Prompt Optimization: Concise prompts reduce tokens
- Fallback: Pre-written tips when AI unavailable
Answer:
- Local Ollama: 2-5 seconds for streaming start
- Full Response: 10-20 seconds for complete tips
- Streaming: First token in ~2 seconds
- Optimization: Keep prompts under 500 tokens
Answer:
- Relevance: Tips specific to user's devices
- Actionability: Clear, implementable suggestions
- Accuracy: Based on actual usage patterns
- User Feedback: Track which tips users implement
- A/B Testing: Test different prompt strategies
Answer:
- User sets energy threshold in profile
- Scheduled job runs every 10 seconds
- Aggregates energy usage per user for last hour
- Compares against threshold
- If exceeded → publishes AlertingEvent to Kafka
- Alert Service consumes event
- Saves alert to database
- Sends email notification
Answer:
@Component
@Slf4j
public class AlertScheduler {
private final UsageRepository usageRepository;
private final UserClient userClient;
private final KafkaTemplate<String, AlertingEvent> kafkaTemplate;
@Scheduled(cron = "*/10 * * * * *") // Every 10 seconds
public void checkEnergyThresholds() {
log.info("Checking energy thresholds...");
List<UserDTO> usersWithAlerting = userClient.getUsersWithAlertingEnabled();
for (UserDTO user : usersWithAlerting) {
Double recentUsage = usageRepository.getRecentUsage(user.getId(), Duration.ofHours(1));
if (recentUsage > user.getEnergyAlertingThreshold()) {
AlertingEvent event = new AlertingEvent();
event.setUserId(user.getId());
event.setCurrentUsage(recentUsage);
event.setThreshold(user.getEnergyAlertingThreshold());
event.setTimestamp(LocalDateTime.now());
kafkaTemplate.send("energy-alerts", event);
log.warn("Alert triggered for user {}: {} exceeds threshold {}",
user.getId(), recentUsage, user.getEnergyAlertingThreshold());
}
}
}
}Answer:
- Development: Mailpit (Port 8025/1025)
- Production: SMTP server (configurable)
- Library: Spring Mail
- Features: HTML templates, attachments, async sending
Answer:
@Service
public class EmailService {
private final JavaMailSender mailSender;
@Async
public void sendAlertEmail(Alert alert, UserDTO user) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(user.getEmail());
helper.setSubject("⚠️ Energy Alert: Threshold Exceeded");
String htmlContent = String.format(
"<h2>Energy Alert</h2>" +
"<p>Your energy consumption has exceeded the threshold.</p>" +
"<p><strong>Current Usage:</strong> %.2f kWh</p>" +
"<p><strong>Threshold:</strong> %.2f kWh</p>" +
"<p><strong>Time:</strong> %s</p>",
alert.getValue(),
alert.getThreshold(),
alert.getTimestamp()
);
helper.setText(htmlContent, true);
mailSender.send(message);
alert.setEmailSent(true);
alertRepository.save(alert);
} catch (MessagingException e) {
log.error("Failed to send email", e);
}
}
}Answer:
- Cooldown Period: Don't send duplicate alerts within 1 hour
- Threshold Hysteresis: Reset only when usage drops below 80% of threshold
- Digest Mode: Option for daily summary instead of immediate alerts
- User Preferences: Users can disable specific alert types
Answer:
public enum AlertSeverity {
LOW, // 0-20% above threshold
MEDIUM, // 20-50% above threshold
HIGH, // 50-100% above threshold
CRITICAL // >100% above threshold
}Answer:
@PutMapping("/api/v1/alert/{alertId}/acknowledge")
public ResponseEntity<Void> acknowledgeAlert(@PathVariable Long alertId) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new AlertNotFoundException(alertId));
alert.setAcknowledged(true);
alert.setAcknowledgedAt(LocalDateTime.now());
alertRepository.save(alert);
return ResponseEntity.ok().build();
}Answer:
- THRESHOLD_EXCEEDED: Energy usage above limit
- DEVICE_OFFLINE: Device not sending data
- UNUSUAL_USAGE: Usage pattern anomaly detected
- SYSTEM: Platform maintenance notifications
Answer:
- email_sent column in alerts table
- sent_at timestamp
- delivery_status: PENDING, SENT, FAILED
- retry_count: Number of delivery attempts
- error_message: Failure reason
Answer:
Current: User-level threshold only
Future Enhancement:
// Device-specific thresholds
@Entity
public class DeviceAlertConfig {
private Long deviceId;
private Double threshold;
private boolean enabled;
private Duration cooldownPeriod;
}Answer:
- Spring Boot Actuator: Health checks, metrics
- Kafka UI: Message monitoring, consumer lag
- Mailpit UI: Email testing and debugging
- InfluxDB UI: Time-series data visualization
- Eureka Dashboard: Service registration status
- Application Logs: SLF4J with structured logging
Answer: Actuator Endpoints:
/actuator/health- Overall health/actuator/health/liveness- Kubernetes liveness/actuator/health/readiness- Kubernetes readiness/actuator/info- Application info/actuator/metrics- JVM and custom metrics
Answer: Unit Tests:
- JUnit 5 with Mockito
- Service layer testing
- Repository testing with @DataJpaTest
Integration Tests:
- @SpringBootTest with TestContainers
- Kafka TestContainers for event testing
- WebTestClient for HTTP testing
Contract Tests:
- Consumer-Driven Contracts
- Spring Cloud Contract
Answer:
@SpringBootTest
@AutoConfigureMockMvc
public class UserServiceTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserRepository userRepository;
@Test
public void testUserRegistration() throws Exception {
UserDTO dto = new UserDTO();
dto.setName("John Doe");
dto.setEmail("john@example.com");
dto.setPassword("password123");
mockMvc.perform(post("/api/v1/user/register")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(dto)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("john@example.com"));
}
}Answer:
@SpringBootTest
@Testcontainers
public class KafkaIntegrationTest {
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:latest"));
@Autowired
private KafkaTemplate<String, EnergyUsageEvent> kafkaTemplate;
@Autowired
private Consumer<String, EnergyUsageEvent> consumer;
@Test
public void testEventPublishing() {
EnergyUsageEvent event = new EnergyUsageEvent();
event.setDeviceId(1L);
event.setEnergyConsumed(10.5);
kafkaTemplate.send("energy-usage", event);
ConsumerRecord<String, EnergyUsageEvent> record =
KafkaTestUtils.getSingleRecord(consumer, "energy-usage");
assertEquals(10.5, record.value().getEnergyConsumed());
}
}Answer: Build Pipeline:
- Code commit triggers GitHub Actions
- Run unit tests
- Run integration tests with TestContainers
- Build Docker images
- Push to Docker Registry
- Deploy to Kubernetes cluster
Deployment Strategy:
- Blue-Green deployment
- Rolling updates for zero downtime
- Automated rollback on failure
Answer:
# config-repo/configs/application-dev.properties
spring.datasource.url=jdbc:mysql://dev-mysql:3306/watt_tracker
kafka.bootstrap-servers=dev-kafka:9092
# config-repo/configs/application-prod.properties
spring.datasource.url=jdbc:mysql://prod-mysql-cluster:3306/watt_tracker
kafka.bootstrap-servers=prod-kafka-cluster:9092Activation: spring.profiles.active=prod
Answer:
# Multi-stage build for each service
FROM eclipse-temurin:21-jdk-alpine as builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Answer:
- Development: Application properties (not for production!)
- Production:
- Kubernetes Secrets
- Vault by HashiCorp
- AWS Secrets Manager
- Environment variables
- Never commit: Passwords, API keys, JWT secrets
Answer:
- SLF4J with Logback
- Lombok @Slf4j for convenience
- Structured Logging: JSON format for log aggregation
- Correlation IDs: Trace requests across services
- ELK Stack: Elasticsearch, Logstash, Kibana (production)
Answer:
@Slf4j
@Service
public class UsageService {
public void processEnergyEvent(EnergyUsageEvent event) {
log.info("Processing energy event for device: {}, consumption: {}",
event.getDeviceId(), event.getEnergyConsumed());
try {
// Process event
log.debug("Event processed successfully");
} catch (Exception e) {
log.error("Failed to process energy event: {}", event, e);
throw new ProcessingException("Event processing failed", e);
}
}
}Answer:
- Spring Cloud Sleuth: Adds trace IDs to logs
- Micrometer Tracing: Metrics and tracing
- Zipkin/Jaeger: Distributed tracing visualization
- Trace Propagation: Pass trace ID in HTTP headers
Answer:
- Database Backups: Hourly incremental, daily full
- Cross-Region Replication: Multi-AZ deployment
- RTO: Recovery Time Objective - 1 hour
- RPO: Recovery Point Objective - 15 minutes
- Runbook: Documented recovery procedures
- Testing: Quarterly DR drills
Answer:
- Horizontal Pod Autoscaler: Kubernetes HPA
- Metrics: CPU/Memory utilization
- Custom Metrics: Request rate, queue depth
- Min/Max Replicas: 2-10 pods per service
- Load Balancing: Eureka client-side load balancing
Answer:
- Tool: Apache JMeter, Gatling
- Scenario: 10,000 concurrent users
- Throughput: 1,000 requests/second
- Latency: P95 < 500ms, P99 < 1s
- Bottlenecks: Identified and optimized Kafka partition count
Answer:
- Throughput: 10,000 events/minute sustained
- Peak: 15,000 events/minute
- Latency: P99 < 100ms (ingestion to Kafka)
- Parallelism: 2 threads processing concurrently
- Batch Size: 100 events per batch for efficiency
Answer:
- Indexing: B-tree indexes on foreign keys and search columns
- Query Optimization: Explain plan analysis
- Connection Pooling: HikariCP (10 connections default)
- Batch Inserts: JdbcTemplate batch updates
- Read Replicas: For read-heavy workloads (production)
Answer: Current:
- Hibernate Second-Level Cache (EhCache)
- Spring Cache abstraction
Planned:
- Redis for distributed caching
- Caffeine for local caching
- Cache-aside pattern for user profiles
Answer:
// Reactor backpressure handling
public Flux<EnergyUsageEvent> processEvents() {
return kafkaReceiver.receive()
.onBackpressureBuffer(1000) // Buffer up to 1000 events
.flatMap(record -> processEvent(record.value())
.subscribeOn(Schedulers.boundedElastic()),
10 // Concurrency limit
);
}Answer:
# Dockerfile ENTRYPOINT
java -Xms512m -Xmx1024m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-jar app.jarAnswer:
- Heap Dumps: Generate on OutOfMemoryError
- Profiling: Java Flight Recorder, VisualVM
- Monitoring: Track heap usage trends
- Best Practices: Close resources, avoid static collections
Answer:
- Start Simple: Monolith first, then extract services
- Event-Driven: Kafka adds complexity but enables scalability
- Observability: Invest in logging and monitoring early
- Data Consistency: Accept eventual consistency in distributed systems
- Testing: Integration tests with TestContainers are essential
- Documentation: Keep API documentation updated
Answer:
- GraphQL: Instead of REST for flexible queries
- gRPC: For internal service communication (faster than REST)
- Service Mesh: Istio for mTLS and traffic management
- CQRS Full: Separate read/write databases from start
- Feature Flags: LaunchDarkly for gradual rollouts
Answer:
- Real-time Dashboards: WebSocket integration
- Machine Learning: Anomaly detection for energy patterns
- Mobile App: React Native application
- Multi-tenancy: SaaS platform support
- IoT Integration: MQTT broker for device connectivity
- Billing: Usage-based billing system
Answer: Technical Skills:
- Built production-ready microservices from scratch
- Implemented event-driven architecture with Kafka
- Integrated AI/ML capabilities using Spring AI
- Strong understanding of distributed systems challenges
Best Practices:
- Security-first (JWT, encryption, input validation)
- Testing strategy (unit, integration, contract tests)
- Monitoring and observability
- CI/CD pipeline design
Problem Solving:
- Chose right tools for each use case (polyglot persistence)
- Handled complex data flow across services
- Implemented resilient error handling
Business Impact:
- End-to-end ownership of complex system
- Performance optimization (10k events/minute)
- Cost-effective solution (local AI, open-source stack)
| Service | Port | URL | Status |
|---|---|---|---|
| Eureka | 8761 | http://localhost:8761 | ✅ UP |
| Config | 8888 | http://localhost:8888 | ✅ UP |
| API Gateway | 9090 | http://localhost:9090 | ✅ UP |
| Device | 8081 | http://localhost:8081 | ✅ UP |
| Ingestion | 8082 | http://localhost:8082 | ✅ UP |
| Usage | 8083 | http://localhost:8083 | ✅ UP |
| Alert | 8084 | http://localhost:8084 | ✅ UP |
| Insight | 8085 | http://localhost:8085 | ✅ UP |
| User | 8086 | http://localhost:8086 | ✅ UP |
| Kafka UI | 8070 | http://localhost:8070 | ✅ UP |
| Mailpit | 8025 | http://localhost:8025 | ✅ UP |
| InfluxDB | 8072 | http://localhost:8072 | ✅ UP |
| React UI | 5173 | http://localhost:5173 | ✅ UP |
- Services: 9 microservices
- Throughput: 10,000 events/minute
- Latency: P99 < 100ms (ingestion)
- Uptime: 99.9% target
- Tech Stack: Spring Boot, Kafka, InfluxDB, React
- AI Response: 2-5 seconds streaming
- Data Retention: 30 days raw, 1 year aggregated
- Deployment: Docker Compose locally, Kubernetes ready
Good luck with your interview! 🚀