2.1 Use Case'lerin Eklenmesi
Hexagonal Mimari'de, iş mantığını daha iyi organize etmek için "Use Case" kavramını kullanabiliriz. Her use case, uygulamanın belirli bir işlevini temsil eder.
2.2 Birden Fazla Adaptör Kullanımı
Giriş ve çıkış tarafında birden fazla adaptör kullanarak, uygulamanın farklı teknolojilerle nasıl entegre olabileceğini gösterelim.
2.3 Validation ve Exception Handling
Giriş portlarında validation işlemlerini ve genel exception handling mekanizmasını ekleyelim.
Şimdi, bu kavramları içeren genişletilmiş bir örnek oluşturalım:
Bu genişletilmiş örnekte, Hexagonal Mimari'nin daha karmaşık bir uygulamasını görebilirsiniz:
- Use Case'ler:
CreateUserUseCase
veGetUserUseCase
olarak ayrı arayüzler tanımlandı. Bu, her bir işlevi daha net bir şekilde ayırmamızı sağlar. - Çoklu Adaptörler:
- Giriş Adaptörleri: REST API (
UserController
) ve CLI (UserCliAdapter
) eklendi. - Çıkış Adaptörleri: JPA Repository (
JpaUserRepository
) ve Email Notification (EmailNotificationAdapter
) eklendi.
- Giriş Adaptörleri: REST API (
- Validation ve Exception Handling:
GlobalExceptionHandler
sınıfı eklenerek merkezi bir hata yönetimi sağlandı. - Dependency Inversion: Tüm bağımlılıklar arayüzler üzerinden yönetiliyor, bu da esneklik sağlıyor.
- Separation of Concerns: İş mantığı (
UserService
), veri erişimi (UserRepository
), ve bildirim gönderme (UserNotification
) ayrı arayüzler ve sınıflarla temsil ediliyor.
Bu yapı sayesinde:
- Yeni giriş veya çıkış adaptörleri kolayca eklenebilir (örneğin, gRPC API veya farklı bir veritabanı).
- İş mantığı, altyapı detaylarından tamamen izole edilmiştir.
- Her bir bileşen bağımsız olarak test edilebilir.
Hexagonal Mimari'nin bu uygulaması, büyük ve karmaşık projelerde bile kodun organize ve sürdürülebilir kalmasını sağlar.
package com.example.hexagonal.domain; public class User { private String id; private String name; private String email; // constructor, getters, setters } // Use Case (Giriş Portu) package com.example.hexagonal.port.in; public interface CreateUserUseCase { User createUser(String name, String email); } public interface GetUserUseCase { User getUserById(String id); } // Çıkış Portu package com.example.hexagonal.port.out; public interface UserRepository { User save(User user); User findById(String id); } public interface UserNotification { void sendWelcomeEmail(User user); } // Giriş Adaptörü - REST Controller package com.example.hexagonal.adapter.in.web; import org.springframework.web.bind.annotation.*; import com.example.hexagonal.port.in.CreateUserUseCase; import com.example.hexagonal.port.in.GetUserUseCase; @RestController @RequestMapping("/users") public class UserController { private final UserCliAdapter userCliAdapter; public UserController( UserCliAdapter userCliAdapter ) {
this. userCliAdapter = userCliAdapter; } @PostMapping public User createUser(@RequestBody CreateUserRequest request) { return userCliAdapter.createUser(request.getName(), request.getEmail()); } @GetMapping("/{id}") public User getUser(@PathVariable String id) { return userCliAdapter.getUserById(id); } } // Giriş Adaptörü - CLI package com.example.hexagonal.adapter.in.cli; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; import com.example.hexagonal.port.in.CreateUserUseCase; import com.example.hexagonal.port.in.GetUserUseCase; @ShellComponent public class UserCliAdapter { private final CreateUserUseCase createUserUseCase; private final GetUserUseCase getUserUseCase; public UserCliAdapter(CreateUserUseCase createUserUseCase, GetUserUseCase getUserUseCase) { this.createUserUseCase = createUserUseCase; this.getUserUseCase = getUserUseCase; } @ShellMethod(value = "Create a new user", key = "create-user") public String createUser(String name, String email) { User user = createUserUseCase.createUser(name, email); return "User created with ID: " + user.getId(); } @ShellMethod(value = "Get user by ID", key = "get-user") public String getUser(String id) { User user = getUserUseCase.getUserById(id); return user != null ? user.toString() : "User not found"; } } // Çıkış Adaptörü - JPA Repository package com.example.hexagonal.adapter.out.persistence; import org.springframework.data.jpa.repository.JpaRepository; import com.example.hexagonal.domain.User; import com.example.hexagonal.port.out.UserRepository; public interface JpaUserRepository extends JpaRepository<User, String>, UserRepository { } // Çıkış Adaptörü - Email Notification package com.example.hexagonal.adapter.out.notification; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import com.example.hexagonal.domain.User; import com.example.hexagonal.port.out.UserNotification; public class EmailNotificationAdapter implements UserNotification { private final JavaMailSender mailSender; public EmailNotificationAdapter(JavaMailSender mailSender) { this.mailSender = mailSender; } @Override public void sendWelcomeEmail(User user) { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(user.getEmail()); message.setSubject("Welcome to our platform!"); message.setText("Dear " + user.getName() + ", welcome to our platform!"); mailSender.send(message); } } // Service (İş Mantığı Uygulaması) package com.example.hexagonal.service; import com.example.hexagonal.domain.User; import com.example.hexagonal.port.in.CreateUserUseCase; import com.example.hexagonal.port.in.GetUserUseCase; import com.example.hexagonal.port.out.UserRepository; import com.example.hexagonal.port.out.UserNotification; public class UserService implements CreateUserUseCase, GetUserUseCase { private final UserRepository userRepository; private final UserNotification userNotification; public UserService(UserRepository userRepository, UserNotification userNotification) { this.userRepository = userRepository; this.userNotification = userNotification; } @Override public User createUser(String name, String email) { User user = new User(); user.setName(name); user.setEmail(email); User savedUser = userRepository.save(user); userNotification.sendWelcomeEmail(savedUser); return savedUser; } @Override public User getUserById(String id) { return userRepository.findById(id); } } // Exception Handling package com.example.hexagonal.exception; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.http.HttpStatus; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UserNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleUserNotFoundException(UserNotFoundException ex) { return new ErrorResponse("User not found", ex.getMessage()); } // Diğer exception handler'lar... }