Skip to content

Commit 9a77524

Browse files
committed
user-service optimizasyon
1 parent 8a7dc31 commit 9a77524

7 files changed

Lines changed: 272 additions & 38 deletions

File tree

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# AI Performance Fixer Backend Gereksinimleri
2+
3+
## Temel Endpointler
4+
5+
### 1. Performans Analiz Endpoint'i
6+
7+
POST /api/performance/analyze
8+
9+
**İstek:**
10+
11+
```json
12+
{
13+
"url": "https://example.com"
14+
}
15+
```
16+
17+
**Yanıt:**
18+
19+
```json
20+
{
21+
"performance": 0.85,
22+
"audits": {
23+
"first-contentful-paint": {
24+
"score": 0.9,
25+
"displayValue": "1.2s",
26+
"description": "First Contentful Paint marks the time at which the first text or image is painted"
27+
},
28+
"largest-contentful-paint": {
29+
"score": 0.8,
30+
"displayValue": "2.5s",
31+
"description": "Largest Contentful Paint marks the time at which the largest text or image is painted"
32+
}
33+
// Diğer önemli metrikleri içerir
34+
},
35+
"timestamp": 1673912345678,
36+
"url": "https://example.com",
37+
"categories": {
38+
"performance": { "score": 0.85 },
39+
"accessibility": { "score": 0.92 },
40+
"best-practices": { "score": 0.87 },
41+
"seo": { "score": 0.95 }
42+
}
43+
}
44+
```
45+
46+
### 2. AI Önerileri Endpoint'i
47+
48+
POST /api/performance/suggestions
49+
50+
**İstek:**
51+
52+
```json
53+
{
54+
"url": "https://example.com"
55+
}
56+
```
57+
58+
**Yanıt:**
59+
60+
```json
61+
{
62+
"suggestions": [
63+
{
64+
"id": "suggestion-1",
65+
"problem": "Optimize edilmemiş görseller",
66+
"severity": "critical",
67+
"solution": "Görselleri WebP formatına dönüştürün ve boyutlarını düşürün",
68+
"codeExample": "<!-- Örnek kod -->\n<picture>\n <source srcset=\"image.webp\" type=\"image/webp\">\n <img src=\"image.jpg\" loading=\"lazy\" alt=\"Açıklama\">\n</picture>",
69+
"resources": ["https://web.dev/optimize-images"],
70+
"implementationDifficulty": "easy"
71+
},
72+
{
73+
"id": "suggestion-2",
74+
"problem": "Render-blocking kaynaklar",
75+
"severity": "major",
76+
"solution": "Kritik CSS'i inline olarak ekleyin ve JS yüklemelerini erteleyebilirsiniz",
77+
"codeExample": "<!-- Kritik CSS -->\n<style>\n /* Kritik stiller burada */\n</style>\n\n<!-- JS erteleme -->\n<script src=\"script.js\" defer></script>",
78+
"resources": ["https://web.dev/render-blocking-resources"],
79+
"implementationDifficulty": "medium"
80+
}
81+
]
82+
}
83+
```
84+
85+
### 3. Performans Geçmişi Endpoint'i (Opsiyonel)
86+
87+
POST /api/performance/history
88+
89+
**İstek:**
90+
91+
```json
92+
{
93+
"url": "https://example.com"
94+
}
95+
```
96+
97+
**Yanıt:**
98+
99+
```json
100+
{
101+
"history": [
102+
{
103+
"id": "analysis-123",
104+
"url": "https://example.com",
105+
"timestamp": 1673912345678,
106+
"performance": 0.85
107+
},
108+
{
109+
"id": "analysis-122",
110+
"url": "https://example.com",
111+
"timestamp": 1673825945678,
112+
"performance": 0.82
113+
}
114+
]
115+
}
116+
```
117+
118+
## Teknik Gereksinimler
119+
120+
- Lighthouse Entegrasyonu: Headless Chrome çalıştırarak Lighthouse analizleri yapılmalı
121+
- Maksimum Yanıt Süresi: Analiz endpoint'i maksimum 60 saniye içinde yanıt vermeli
122+
- AI Modeli: Gemini API entegrasyonu ile performans önerileri üretilmeli
123+
- Hata İşleme: URL'in erişilebilir olmama durumu için uygun hata mesajları döndürmeli
124+
125+
## Güvenlik
126+
127+
- URL doğrulama ve sanitizasyon
128+
- Rate limiting (IP başına dakikada 5 istek)
129+
130+
## Notlar
131+
132+
- Tüm endpoint'ler JSON formatında yanıt döndürmeli
133+
- Backend'de performans analizi yapılmalı, frontend'de değil
134+
- Önbellek mekanizması ile aynı URL için kısa süre içinde yapılan tekrarlı istekler optimize edilmeli

redis-client-lib/src/main/java/com/craftpilot/redis/config/RedisClientProperties.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,39 @@ public class RedisClientProperties {
7878
/**
7979
* Devre kesici başarısızlık eşiği yüzdesi
8080
*/
81+
@Builder.Default
8182
private float circuitBreakerFailureRateThreshold = 50.0f;
8283

8384
/**
8485
* Devre kesici açık durumda bekleme süresi
8586
*/
87+
@Builder.Default
8688
private Duration circuitBreakerWaitDurationInOpenState = Duration.ofSeconds(10);
8789

8890
/**
8991
* Devre kesici yarı açık durumda izin verilen çağrı sayısı
9092
*/
93+
@Builder.Default
9194
private int circuitBreakerPermittedCallsInHalfOpenState = 3;
9295

96+
/**
97+
* Devre kesici izleme penceresi türü
98+
*/
99+
@Builder.Default
100+
private String circuitBreakerSlidingWindowType = "COUNT_BASED";
101+
102+
/**
103+
* Devre kesici izleme penceresi boyutu
104+
*/
105+
@Builder.Default
106+
private int circuitBreakerSlidingWindowSize = 10;
107+
108+
/**
109+
* Devre kesici minimum çağrı sayısı
110+
*/
111+
@Builder.Default
112+
private int circuitBreakerMinimumNumberOfCalls = 3;
113+
93114
/**
94115
* Retry etkin
95116
*/

user-service/src/main/java/com/craftpilot/userservice/config/RedisConfig.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import org.springframework.context.annotation.Configuration;
1111
import org.springframework.context.annotation.Import;
1212
import org.springframework.context.annotation.Primary;
13+
import java.time.Duration;
14+
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
15+
import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigCustomizer;
1316

1417
/**
1518
* Redis özelleştirmeleri için konfigürasyon sınıfı.
@@ -22,6 +25,23 @@
2225
@Import(RedisClientAutoConfiguration.class)
2326
public class RedisConfig {
2427

28+
/**
29+
* Redis için devre kesici (circuit breaker) özelleştirmesi
30+
*/
31+
@Bean
32+
public CircuitBreakerConfigCustomizer redisCircuitBreakerCustomizer() {
33+
return CircuitBreakerConfigCustomizer
34+
.of("redisCircuitBreaker", builder -> builder
35+
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
36+
.slidingWindowSize(10)
37+
.failureRateThreshold(50.0f)
38+
.waitDurationInOpenState(Duration.ofSeconds(10)) // 30 saniyeden 10'a düşürüldü
39+
.permittedNumberOfCallsInHalfOpenState(3)
40+
.minimumNumberOfCalls(3) // 5'ten 3'e düşürüldü daha hızlı tepki vermesi için
41+
.recordExceptions(Exception.class)
42+
);
43+
}
44+
2545
/**
2646
* ReactiveRedisService için primary bean tanımı.
2747
* Bu, ReactiveCacheService ile çakışmaları önler.

user-service/src/main/java/com/craftpilot/userservice/controller/UserPreferenceController.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import java.util.List;
1818
import java.util.Map;
19+
import java.time.Duration;
20+
import java.util.concurrent.TimeoutException;
1921

2022
@RestController
2123
@RequestMapping("/users/{userId}/preferences")
@@ -72,18 +74,22 @@ public Mono<ResponseEntity<UserPreference>> updateUserPreferences(
7274

7375
return userPreferenceService.saveUserPreferences(preference)
7476
.map(ResponseEntity::ok)
75-
.timeout(java.time.Duration.ofSeconds(3)) // 8 saniyeden 3 saniyeye düşürülmeli
77+
// 8 saniyeden 3 saniyeye indiriyoruz - bu değeri application.yml'den alacak şekilde düzenlenebilir
78+
.timeout(Duration.ofMillis(1500))
7679
.doOnSuccess(response -> log.info("Kullanıcı tercihleri başarıyla güncellendi: userId={}", userId))
77-
.doOnError(e -> log.error("Kullanıcı tercihleri güncellenirken hata: userId={}, error={}", userId, e.getMessage()))
78-
.onErrorResume(e -> {
79-
log.error("Tercih güncelleme hatası (fallback çalıştırılıyor): {}", e.getMessage());
80-
return createFallbackPreference(userId, request)
81-
.map(fallback -> ResponseEntity.ok(fallback))
82-
.onErrorReturn(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
83-
});
80+
.doOnError(e -> {
81+
if (e instanceof TimeoutException) {
82+
log.error("Kullanıcı tercihleri güncelleme zaman aşımına uğradı: userId={}", userId);
83+
} else {
84+
log.error("Kullanıcı tercihleri güncellenirken hata: userId={}, error={}", userId, e.getMessage());
85+
}
86+
})
87+
.onErrorResume(TimeoutException.class, e -> Mono.just(ResponseEntity.status(HttpStatus.ACCEPTED)
88+
.body(preference))) // Timeout durumunda 202 Accepted dönüyoruz
89+
.onErrorResume(e -> Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build()));
8490
} catch (Exception e) {
85-
log.error("İstek işlenirken beklenmeyen hata: userId={}, error={}", userId, e.getMessage());
86-
return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).build());
91+
log.error("Kullanıcı tercihleri güncellenirken istisna: userId={}, error={}", userId, e.getMessage());
92+
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
8793
}
8894
}
8995

user-service/src/main/java/com/craftpilot/userservice/service/RedisCacheService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,16 @@ public Mono<UserPreference> getUserPreferences(String userId) {
6767
public Mono<Boolean> saveUserPreferences(UserPreference preference) {
6868
log.debug("Kullanıcı tercihleri Redis'e kaydediliyor: userId={}", preference.getUserId());
6969
preference.setUpdatedAt(System.currentTimeMillis());
70-
return cacheService.cache(PREFERENCE_KEY_PREFIX + preference.getUserId(), preference);
70+
return cacheService.cache(PREFERENCE_KEY_PREFIX + preference.getUserId(), preference)
71+
.doOnSuccess(result -> {
72+
if (Boolean.TRUE.equals(result)) {
73+
log.debug("Kullanıcı tercihleri başarıyla kaydedildi: userId={}", preference.getUserId());
74+
} else {
75+
log.warn("Kullanıcı tercihleri kaydedilemedi: userId={}", preference.getUserId());
76+
}
77+
})
78+
.doOnError(err -> log.error("Kullanıcı tercihleri kaydedilirken hata: userId={}, error={}",
79+
preference.getUserId(), err.getMessage()));
7180
}
7281

7382
public Mono<Boolean> deleteUserPreferences(String userId) {

user-service/src/main/java/com/craftpilot/userservice/service/UserPreferenceService.java

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public class UserPreferenceService {
2828
@Value("${kafka.topics.user-preferences:user-preferences}")
2929
private String userPreferencesTopic;
3030

31+
@Value("${user-preference.operation.timeout:1000}")
32+
private long operationTimeoutMillis;
33+
3134
@CircuitBreaker(name = "userPreferences", fallbackMethod = "getDefaultPreferences")
3235
public Mono<UserPreference> getUserPreferences(String userId) {
3336
log.info("Kullanıcı tercihleri getiriliyor: userId={}", userId);
@@ -51,15 +54,31 @@ public Mono<UserPreference> getDefaultPreferences(String userId, Throwable t) {
5154

5255
public Mono<UserPreference> saveUserPreferences(UserPreference preferences) {
5356
log.info("Kullanıcı tercihleri kaydediliyor: userId={}", preferences.getUserId());
57+
58+
// Redis'e kayıt işlemini timeout ile sarmalıyoruz
5459
return redisCacheService.saveUserPreferences(preferences)
55-
.then(Mono.defer(() -> {
56-
// Kafka'ya bildirim gönder
57-
kafkaTemplate.send(userPreferencesTopic, preferences.getUserId(), preferences);
58-
return Mono.just(preferences);
60+
.timeout(Duration.ofMillis(operationTimeoutMillis))
61+
.doOnError(e -> {
62+
if (e instanceof TimeoutException) {
63+
log.warn("Redis kaydetme işlemi zaman aşımına uğradı, ancak işlem arka planda devam edecek: userId={}",
64+
preferences.getUserId());
65+
} else {
66+
log.error("Redis kaydetme işlemi sırasında hata: userId={}, error={}",
67+
preferences.getUserId(), e.getMessage());
68+
}
69+
})
70+
.onErrorResume(e -> Mono.empty())
71+
// Kafka'ya bildirimi non-blocking yapıyoruz
72+
.then(Mono.fromCallable(() -> {
73+
// Kafka'ya bildirim gönderme işlemini asenkron yapıyoruz
74+
try {
75+
kafkaTemplate.sendDefault(preferences.getUserId(), preferences);
76+
} catch (Exception e) {
77+
log.warn("Kafka mesajı gönderilirken hata oluştu: {}", e.getMessage());
78+
}
79+
return preferences;
5980
}))
60-
.doOnSuccess(pref -> log.debug("Kullanıcı tercihleri başarıyla kaydedildi: userId={}", preferences.getUserId()))
61-
.doOnError(e -> log.error("Kullanıcı tercihleri kaydedilirken hata: userId={}, error={}",
62-
preferences.getUserId(), e.getMessage()));
81+
.doOnSuccess(pref -> log.debug("Kullanıcı tercihleri işlemi tamamlandı: userId={}", preferences.getUserId()));
6382
}
6483

6584
public Mono<UserPreference> updateTheme(String userId, String theme) {
@@ -74,20 +93,23 @@ public Mono<UserPreference> updateTheme(String userId, String theme) {
7493
pref.setTheme(theme);
7594
pref.setUpdatedAt(System.currentTimeMillis());
7695
return redisCacheService.saveUserPreferences(pref)
77-
.timeout(Duration.ofSeconds(8))
96+
.timeout(Duration.ofMillis(operationTimeoutMillis))
97+
.onErrorResume(e -> {
98+
log.warn("Redis teması güncelleme hatası (arka planda devam edecek): {}", e.getMessage());
99+
return Mono.just(Boolean.TRUE); // İşlemi devam ettir
100+
})
78101
.thenReturn(pref);
79102
})
80103
.doOnNext(saved -> {
81-
log.debug("Tema başarıyla güncellendi: userId={}, theme={}", userId, theme);
82-
// Sadece tema değişikliğini event olarak yayınla
83-
publishPreferenceUpdated(userId, "theme", theme);
104+
// Tema değişikliğini asenkron olarak event olarak yayınla
105+
try {
106+
publishPreferenceUpdated(userId, "theme", theme);
107+
} catch (Exception e) {
108+
log.warn("Tema değişikliği yayınlanırken hata: {}", e.getMessage());
109+
}
84110
})
85111
.doOnError(e -> log.error("Tema güncellenirken hata: userId={}, theme={}, error={}",
86-
userId, theme, e.getMessage()))
87-
.onErrorResume(e -> {
88-
log.warn("Tema güncelleme hatası için fallback çalıştırılıyor: userId={}", userId);
89-
return createFallbackThemePreference(userId, theme);
90-
});
112+
userId, theme, e.getMessage()));
91113
}
92114

93115
private Mono<UserPreference> createFallbackThemePreference(String userId, String theme) {
@@ -221,6 +243,7 @@ private void publishPreferenceUpdated(String userId, String preferenceType, Stri
221243
event.put("value", value);
222244
event.put("timestamp", System.currentTimeMillis());
223245

246+
// Asenkron gönderim - operasyonu bloklamıyor
224247
kafkaTemplate.send(userPreferencesTopic, userId, event)
225248
.whenComplete((result, ex) -> {
226249
if (ex != null) {

0 commit comments

Comments
 (0)