Backend Geliştirme · 01.01.2025
Java’da Temiz Kod: Spring Boot Geliştiricileri İçin Rehber
Spring Boot uygulamalarında okunabilir, bakımı kolay Java kodu yazmak için geliştirici empatisine ve kod kalitesine odaklanan pratikler.
Java'da Temiz Kod: Spring Boot Geliştiricileri İçin Rehber
Spring Boot geliştiricileri olarak yalnızca uygulama değil, ekip arkadaşlarımızın yıllarca üzerinde çalışacağı kod tabanları oluşturuyoruz. Temiz ve bakımı kolay Java kodu, ekipteki herkesin işini kolaylaştırır.
Temiz Java Kodunun Temel İlkeleri
1. Açık Sınıf Yapısı
Sorumlulukları tek bir sınıfta toplamak yerine sınıfların görevlerini belirgin tutun. Aşağıdaki örnek, kullanıcı kaydını odaklı bir servis ve olay yayınlama yaklaşımıyla ele alır.
// Bad: Mixing concerns and unclear responsibilities
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
@Autowired
private PaymentService paymentService;
public void registerUser(UserDTO dto) {
User user = new User(dto);
userRepository.save(user);
emailService.sendWelcomeEmail(user);
paymentService.setupBillingAccount(user);
}
}
// Good: Single Responsibility and Clear Structure
@Service
@RequiredArgsConstructor
public class UserRegistrationService {
private final UserRepository userRepository;
private final UserRegistrationEventPublisher eventPublisher;
@Transactional
public User registerUser(UserRegistrationRequest request) {
User user = UserMapper.toEntity(request);
validateUserData(user);
User savedUser = userRepository.save(user);
eventPublisher.publishUserRegistered(savedUser);
return savedUser;
}
}
2. Etkili İstisna Yönetimi
Her hatayı genel bir sunucu hatasına dönüştürmek yerine bulunamayan kullanıcı ve erişim reddi gibi durumları ayrı ele alın.
// Bad: Generic exception handling
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
try {
return ResponseEntity.ok(userService.getUser(id));
} catch (Exception e) {
return ResponseEntity.status(500).build();
}
}
// Good: Specific exception handling with custom exceptions
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
try {
UserResponse user = userService.getUser(id);
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
} catch (UserAccessDeniedException e) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, e.getMessage());
}
}
3. Temiz Repository Yaklaşımı
Karmaşık sorguları tek bir metotta biriktirmek yerine sorgu koşullarını ayrı specification bileşenlerinde ifade edebilirsiniz.
// Bad: Complex queries in repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.email = ?1 AND u.status = 'ACTIVE' " +
"AND u.lastLoginDate > ?2 AND u.subscriptionType = ?3")
Optional<User> findActiveUserByEmailAndSubscription(String email,
LocalDateTime date, String subscriptionType);
}
// Good: Using specification pattern for complex queries
@Repository
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {
Optional<User> findByEmail(String email);
}
// Separate specification class
public class UserSpecifications {
public static Specification<User> isActiveUser() {
return (root, query, cb) -> cb.equal(root.get("status"), "ACTIVE");
}
public static Specification<User> hasRecentLogin(LocalDateTime date) {
return (root, query, cb) -> cb.greaterThan(root.get("lastLoginDate"), date);
}
}
Spring Boot İçin İyi Pratikler
1. Yapılandırma Yönetimi
SMTP adresi ve port gibi değerleri kod içine sabitlemek yerine yapılandırma özelliklerinden alın. Doğrulama kuralları da yapılandırmanın beklenen biçimde olmasını sağlar.
// Bad: Hardcoded configuration
@Service
public class EmailService {
private final String smtpServer = "smtp.company.com";
private final int port = 587;
public void sendEmail() {
// Using hardcoded values
}
}
// Good: Using configuration properties
@ConfigurationProperties(prefix = "mail")
@Validated
public record MailProperties(
@NotBlank String smtpServer,
@Min(1) @Max(65535) int port,
@NotBlank String username,
@Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$")
String fromAddress
) {}
@Service
@RequiredArgsConstructor
public class EmailService {
private final MailProperties mailProperties;
}
2. REST API Tasarımı
Tutarlı yanıt yapıları, uygun DTO'lar ve doğrulama kullanın. Kaynak oluşturma işlemleri için uygun HTTP durum kodunu belirtin.
// Bad: Inconsistent API responses
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public User createUser(@RequestBody UserDTO dto) {
return userService.createUser(dto);
}
@GetMapping("/{id}")
public ResponseEntity<?> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUser(id));
}
}
// Good: Consistent API responses with proper DTOs
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<UserResponse> createUser(
@Valid @RequestBody UserCreateRequest request) {
UserResponse user = userService.createUser(request);
return ApiResponse.success(user);
}
@GetMapping("/{id}")
public ApiResponse<UserResponse> getUser(@PathVariable Long id) {
UserResponse user = userService.getUser(id);
return ApiResponse.success(user);
}
}
3. Servis Katmanının Düzenlenmesi
İş kurallarını, veri kaydını ve bildirimleri birbirinden ayırın. Alan olayları, farklı sorumlulukların bağımsız bileşenlere taşınmasını kolaylaştırabilir.
// Bad: Mixed business logic
@Service
public class OrderService {
public Order createOrder(OrderDTO dto) {
validateStock(dto);
calculatePrice(dto);
updateInventory(dto);
notifyUser(dto);
return saveOrder(dto);
}
}
// Good: Using domain events and separate services
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final OrderDomainService domainService;
private final ApplicationEventPublisher eventPublisher;
@Transactional
public OrderResponse createOrder(OrderCreateRequest request) {
Order order = domainService.validateAndCreateOrder(request);
Order savedOrder = orderRepository.save(order);
eventPublisher.publishEvent(new OrderCreatedEvent(savedOrder));
return OrderMapper.toResponse(savedOrder);
}
}
Test Yazarken İyi Pratikler
1. Birim Testleri
Testin başlangıç koşulunu, yapılan işlemi ve beklenen sonucu açıkça gösterin. Given–When–Then yapısı ve açıklayıcı isimler, testin hangi davranışı doğruladığını anlatır.
// Bad: Unclear test structure
@Test
void testUserCreation() {
User user = new User();
user.setName("John");
userService.save(user);
assertTrue(userService.findById(user.getId()).isPresent());
}
// Good: Clear test structure with BDD style
@Test
void givenValidUserData_whenCreatingUser_thenUserIsSavedSuccessfully() {
// Given
UserCreateRequest request = UserCreateRequest.builder()
.name("John Doe")
.email("john@example.com")
.build();
// When
UserResponse response = userService.createUser(request);
// Then
assertThat(response)
.isNotNull()
.satisfies(user -> {
assertThat(user.getName()).isEqualTo("John Doe");
assertThat(user.getEmail()).isEqualTo("john@example.com");
});
}
Sonuç
Spring Boot uygulamalarında temiz Java kodu yazmak, okunabilir, bakımı kolay ve sağlam sistemler oluşturmak demektir. Kod kalitesini değerlendirirken onu okuyan geliştiricinin ne kadar zorlandığını da düşünmeliyiz.
Temel Çıkarımlar
- Spring Boot'un yerleşik yaklaşımlarını ve iyi pratiklerini izleyin.
- Uygun istisna yönetimi ve doğrulama kullanın.
- Servisleri odaklı tutun ve uygun yerlerde alan olaylarından yararlanın.
- Açık ve anlamlı testler yazın.
- Sabit değerler yerine yapılandırma özellikleri kullanın.
Bu pratikler, yalnızca çalışan değil, tüm ekibin üzerinde rahatça çalışabileceği uygulamalar oluşturmaya yardımcı olur.