Microservislerde Temiz Kod Prensipleri

Devrim Ozcay
4 min read4 days ago

--

Selamlar herkese bu yazımda, microservislerle çalışırken nerele dikkat edilmesi gerektiği konusuna değinmek istedim. Özellikle büyük projelerde bu çok önemli bir konudur. Çünkü projelerin karmaşıklığı artıkça yönetimi zorlaşır. İsterseniz yavaştan örnek bir proje ve servis üzerinden ilerleyelim.

Burada örnek bir microservis geliştireceğim ve ismini user-service olarak ayarlayacağım.Evet şimdi devam edelim.Büyük bir e-ticaret platformu seviyesinde, User Service gibi kritik bir mikroservisin temiz kod prensipleri, Aspect-Oriented Programming (AOP), interceptor’lar, ve global exception handling gibi ileri seviye tekniklerle detaylandırılması önemlidir. Bu adımları kullanarak, kodu daha yönetilebilir, sürdürülebilir ve hata toleranslı hale getiririz. Daha sonrasında Kubernetes’ e de değinmeyi hedefliyorum.

Aşağıda, User Service’i bu gelişmiş konseptlerle nasıl inşa edebileceğimizi adım adım açıklayacağım.

1. User Service Yapısının Detaylandırılması

Öncelikle User Service’in gelişmiş yapısını tasarlayalım. Bu serviste şu özellikler olacak:

  • Kullanıcı kaydı, giriş işlemleri ve JWT ile kimlik doğrulama.
  • AOP kullanarak loglama, güvenlik kontrolleri gibi çapraz kesen (cross-cutting) işlemlerin yönetimi.
  • Global Exception Handling ile merkezi hata yönetimi.
  • Interceptor kullanarak istek öncesi ve sonrası özel işlemler.
  • Clean Code prensiplerine uygun bir yapı ile.

2. User Entity ve Repository

Öncelikle, User Entity ve UserRepository’yi sade ve anlaşılır bir şekilde geliştirelim. Tüm bağımlılıklar ve iş mantığı gerektiği kadar minimal tutulacak.

a. User Entity

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
// Getters and Setters
}

b. UserRepository

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}

3. Service Katmanı: Temiz İş Mantığı

Service katmanı, iş mantığının yer aldığı katmandır ve her zaman temiz, anlaşılır ve kolay yönetilebilir olmalıdır. Exception handling burada doğrudan yapılmayacak, çünkü merkezi hata yönetimi ekleyeceğiz.

@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User registerUser(User user) {
if (userRepository.findByUsername(user.getUsername()).isPresent()) {
throw new UserAlreadyExistsException("Username is already taken");
}
String hashedPassword = hashPassword(user.getPassword());
user.setPassword(hashedPassword);
return userRepository.save(user);
}
private String hashPassword(String password) {
return pbkdf2_sha256.hash(password);
}
}
  • Clean Code prensiplerine göre, her metot sadece bir iş yapar ve servis mantığı sadeleştirilir.
  • SRP (Single Responsibility Principle): Her sınıf ve metot tek bir sorumlulukla sınırlıdır.
  • Kapsüllü Hashleme: Parola hashleme ayrı bir metoda çıkarılır, böylece test edilebilirliği artırılır.

4. AOP ile Loglama ve Güvenlik Kontrolleri

Aspect-Oriented Programming (AOP), çapraz kesen işlemleri yönetmek için kullanılır. Bu, örneğin loglama, performans takibi, ve güvenlik kontrollerini merkezi hale getirir.

a. Loglama Aspect’i

Her servisin başlangıcı ve bitişinde loglama yaparak performans ölçümleri ve hata tespitlerini kolaylaştırabiliriz.

@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(* com.example.userservice..*(..))")
public void logBefore(JoinPoint joinPoint) {
logger.info("Starting method: " + joinPoint.getSignature().getName());
}
@AfterReturning(value = "execution(* com.example.userservice..*(..))", returning = "result")
public void logAfter(JoinPoint joinPoint, Object result) {
logger.info("Completed method: " + joinPoint.getSignature().getName() + " with result: " + result);
}
}
  • @Before ve @AfterReturning anotasyonları ile metotlar başlamadan ve tamamlandıktan sonra loglama yapılıyor.
  • Loglama sayesinde sistemin nerede yavaşladığı veya hangi işlemlerin ne kadar sürdüğü analiz edilebilir.

b. Güvenlik Aspect’i

Bazı metotlara erişmeden önce yetkilendirme kontrolleri yapmak için güvenlik aspect’i kullanabiliriz.

@Aspect
@Component
public class SecurityAspect {
@Before("execution(* com.example.userservice..*(..)) && @annotation(Secured)")
public void checkAuthorization(JoinPoint joinPoint) {
// JWT token'ı kontrol etme
if (!isUserAuthorized()) {
throw new UnauthorizedAccessException("User is not authorized to access this resource");
}
}
private boolean isUserAuthorized() {
// JWT token doğrulama mantığı
// Token'dan kullanıcı rollerini ve yetkilerini kontrol etme
return true; // Örnek olarak
}
}
  • Bu yapı, @Secured anotasyonu eklenmiş metotlar için yetkilendirme kontrolü sağlar.
  • Merkezi bir yerde tüm güvenlik kontrolünü yöneterek tekrarları engelleriz.

5. Global Exception Handling

Exception handling işlemini merkezi bir yerden yapmak, tüm servislerde tekrar eden hata yönetim kodlarını ortadan kaldırır ve temiz bir yapı sağlar. Spring Boot’ta @ControllerAdvice ile merkezi exception handling uygulanabilir.

@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserAlreadyExistsException.class)
public ResponseEntity<String> handleUserAlreadyExists(UserAlreadyExistsException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred: " + ex.getMessage());
}
}
  • @ControllerAdvice: Bu anotasyon, tüm controller’lar için merkezi hata yönetimi sağlar.
  • Exception türlerine göre farklı cevaplar döndürülür (örneğin, kullanıcı zaten varsa 409 Conflict).

6. Interceptor ile İstek/Çıkış Yönetimi

Interceptor’lar, her bir HTTP isteği geldiğinde veya çıktı gönderildiğinde işlemler gerçekleştirmek için kullanılır. Bu, örneğin JWT token’ı doğrulamak veya belirli kullanıcı bilgilerini her istekte loglamak için idealdir.

@Component
public class RequestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (!isValidToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return true;
}
private boolean isValidToken(String token) {
// Token doğrulama mantığı
return true; // Örnek olarak
}
}
  • preHandle(): Her isteğin öncesinde çalışır ve belirli koşullara göre isteğin devam etmesine veya iptal edilmesine karar verir.
  • JWT token doğrulama veya belirli loglama işlemleri için kullanılabilir.

7. User Service için Kubernetes Dağıtımı

Servisin dağıtımı için Kubernetes’e ConfigMap ve Secret tanımlayarak çevresel değişkenleri güvenli ve dinamik bir şekilde yönetebiliriz.

a. ConfigMap ve Secret Tanımları

Kubernetes üzerinde, servis için konfigürasyonlar ve hassas veriler ayrı kaynaklar olarak tanımlanır.

ConfigMap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-config
data:
DATABASE_URL: jdbc:postgresql://db:5432/userdb
CACHE_SIZE: "1024"

Secret.yaml:

apiVersion: v1
kind: Secret
metadata:
name: user-service-secrets
type: Opaque
data:
db-password: YWRtaW5wYXNz

b. Deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: myregistry/user-service:latest
env:
- name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: user-service-config
key: DATABASE_URL
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: user-service-secrets
key: db-password

Bu yapı, hassas bilgilerin ve dinamik konfigürasyonların Kubernetes üzerinden güvenli ve merkezi olarak yönetilmesini sağlar.

Sonuç:

Bu aşamalarda User Service’i büyük bir e-ticaret platformuna uygun şekilde AOP, interceptor’lar, merkezi hata yönetimi, JWT kimlik doğrulama ve Kubernetes üzerinde ölçeklenebilir hale getirdik. Tüm bu bileşenler, esneklik ve güvenilirliği artırırken clean code prensiplerine uygun, test edilebilir ve sürdürülebilir bir yapı oluşturur.

Buraya kadar okuduğunuz için teşekkür ederim :)

--

--