← Назад к вопросам
Как выглядит архитектурно последний проект
1.7 Middle🔥 111 комментариев
#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ
Архитектура последнего проекта
Мой последний production проект — это E-commerce платформа с микросервисной архитектурой, разработанная в соответствии с лучшими практиками DDD и Clean Architecture.
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ API Gateway (Kong) │
│ - Rate Limiting │
│ - Authentication │
│ - Request Routing │
└──────┬────────┬────────┬────────┬────────────────────────────┘
│ │ │ │
┌───┴──┐ ┌──┴──┐ ┌───┴──┐ ┌──┴────┐
│Users │ │Order│ │Search│ │Payment│
│ MS │ │ MS │ │ MS │ │ MS │
└───┬──┘ └──┬──┘ └───┬──┘ └──┬────┘
│ │ │ │
↓ ↓ ↓ ↓
┌─────────────────────────────────┐
│ Shared Infrastructure │
│ - Message Broker (RabbitMQ) │
│ - Cache (Redis) │
│ - Service Discovery (Consul) │
│ - Logging (ELK Stack) │
│ - Monitoring (Prometheus) │
└─────────────────────────────────┘
Слоистая архитектура каждого микросервиса
Уровень архитектуры (по слоям):
Presentation Layer
├── REST Controllers
├── Request/Response DTOs
└── Exception Handlers
↓
Application Layer
├── UseCases (Orchestration)
├── DTOConverters
└── Validators
↓
Domain Layer
├── Entities
├── ValueObjects
├── Domain Services
├── Repository Interfaces
└── Domain Events
↓
Infrastructure Layer
├── Repository Implementations (JPA)
├── External Service Clients
├── Event Publishers
└── Database Migrations (Goose)
Структура проекта (Users Microservice пример)
src/main/
├── java/com/example/users/
│ ├── api/
│ │ ├── controller/
│ │ │ ├── UserController.java
│ │ │ └── AuthController.java
│ │ ├── dto/
│ │ │ ├── CreateUserRequest.java
│ │ │ ├── UserResponse.java
│ │ │ └── UpdateUserRequest.java
│ │ └── exception/
│ │ ├── UserNotFoundException.java
│ │ └── GlobalExceptionHandler.java
│ │
│ ├── application/
│ │ ├── service/
│ │ │ ├── CreateUserUseCase.java
│ │ │ ├── UpdateUserUseCase.java
│ │ │ ├── GetUserUseCase.java
│ │ │ └── AuthUseCase.java
│ │ ├── mapper/
│ │ │ └── UserDtoMapper.java
│ │ └── validator/
│ │ └── UserValidator.java
│ │
│ ├── domain/
│ │ ├── entity/
│ │ │ ├── User.java
│ │ │ ├── UserEmail.java (ValueObject)
│ │ │ └── UserRole.java (ValueObject)
│ │ ├── service/
│ │ │ └── PasswordHashingService.java
│ │ ├── event/
│ │ │ └── UserCreatedEvent.java
│ │ ├── repository/
│ │ │ └── UserRepository.java (interface)
│ │ └── exception/
│ │ └── DuplicateEmailException.java
│ │
│ └── infrastructure/
│ ├── persistence/
│ │ ├── JpaUserRepository.java
│ │ ├── UserJpaEntity.java
│ │ └── UserMapper.java
│ ├── external/
│ │ └── EmailServiceClient.java
│ ├── event/
│ │ └── UserEventPublisher.java
│ └── config/
│ ├── JpaConfig.java
│ ├── BeanConfig.java
│ └── SecurityConfig.java
│
└── resources/
├── application.yml
├── application-dev.yml
├── application-prod.yml
└── db/migration/
├── 0001_create_users_table.sql
├── 0002_add_email_index.sql
└── 0003_add_roles_table.sql
Domain Entity (DDD Подход)
@Entity
@Table(name = "users")
public class User {
@Id
private UUID id;
@Embedded
private UserEmail email; // ValueObject
@Column
private String passwordHash;
@Column
private String name;
@ElementCollection
@CollectionTable(name = "user_roles")
private Set<UserRole> roles; // ValueObject
@Column
private LocalDateTime createdAt;
@Column
private LocalDateTime updatedAt;
@Column
private UserStatus status; // Enum
// Domain Events
@Transient
private List<DomainEvent> domainEvents = new ArrayList<>();
// Factory method
public static User create(String email, String name, String password) {
User user = new User();
user.id = UUID.randomUUID();
user.email = new UserEmail(email); // Валидация в ValueObject
user.name = name;
user.passwordHash = PasswordHasher.hash(password);
user.roles = Set.of(UserRole.USER);
user.status = UserStatus.ACTIVE;
user.createdAt = LocalDateTime.now(UTC);
user.updatedAt = LocalDateTime.now(UTC);
// Публикуем доменное событие
user.addDomainEvent(new UserCreatedEvent(user.id, user.email.value()));
return user;
}
// Business logic
public void updateEmail(UserEmail newEmail) {
if (newEmail.equals(this.email)) {
throw new InvalidEmailException("Email is already in use");
}
this.email = newEmail;
this.updatedAt = LocalDateTime.now(UTC);
this.addDomainEvent(new UserEmailChangedEvent(this.id, newEmail.value()));
}
public void assignRole(UserRole role) {
this.roles.add(role);
this.addDomainEvent(new UserRoleAssignedEvent(this.id, role));
}
private void addDomainEvent(DomainEvent event) {
this.domainEvents.add(event);
}
public List<DomainEvent> getDomainEvents() {
return new ArrayList<>(domainEvents);
}
public void clearDomainEvents() {
domainEvents.clear();
}
}
// ValueObject — неизменяемый, никогда не null
@Embeddable
public class UserEmail {
@Column(name = "email")
private String value;
public UserEmail(String value) {
if (!isValid(value)) {
throw new InvalidEmailException("Invalid email: " + value);
}
this.value = value;
}
private static boolean isValid(String email) {
return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
public String value() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserEmail that = (UserEmail) o;
return value.equals(that.value);
}
}
UseCase (Application Service)
@Service
@Transactional
public class CreateUserUseCase {
private final UserRepository userRepository;
private final PasswordHashingService passwordHashingService;
private final UserEventPublisher eventPublisher;
private final UserValidator validator;
@Autowired
public CreateUserUseCase(
UserRepository userRepository,
PasswordHashingService passwordHashingService,
UserEventPublisher eventPublisher,
UserValidator validator
) {
this.userRepository = userRepository;
this.passwordHashingService = passwordHashingService;
this.eventPublisher = eventPublisher;
this.validator = validator;
}
public UserResponse execute(CreateUserRequest request) {
// Валидация
validator.validate(request);
// Проверка дубликатов
if (userRepository.existsByEmail(request.getEmail())) {
throw new DuplicateEmailException("Email already in use");
}
// Бизнес-логика: создаём пользователя (в Domain)
User user = User.create(
request.getEmail(),
request.getName(),
request.getPassword()
);
// Сохраняем
User savedUser = userRepository.save(user);
// Публикуем события (асинхронно)
user.getDomainEvents().forEach(eventPublisher::publish);
user.clearDomainEvents();
// Конвертируем в DTO
return UserDtoMapper.toResponse(savedUser);
}
}
REST Controller
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private final CreateUserUseCase createUserUseCase;
private final GetUserUseCase getUserUseCase;
private final UpdateUserUseCase updateUserUseCase;
@PostMapping
public ResponseEntity<UserResponse> createUser(@RequestBody CreateUserRequest request) {
UserResponse response = createUserUseCase.execute(request);
return ResponseEntity.status(201).body(response);
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable UUID id) {
UserResponse response = getUserUseCase.execute(id);
return ResponseEntity.ok(response);
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(
@PathVariable UUID id,
@RequestBody UpdateUserRequest request
) {
UserResponse response = updateUserUseCase.execute(id, request);
return ResponseEntity.ok(response);
}
}
Event-Driven Communication (между сервисами)
// Domain Event
public class UserCreatedEvent extends DomainEvent {
private final UUID userId;
private final String email;
public UserCreatedEvent(UUID userId, String email) {
this.userId = userId;
this.email = email;
}
}
// Publisher
@Component
public class UserEventPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;
public void publish(DomainEvent event) {
String json = objectMapper.writeValueAsString(event);
rabbitTemplate.convertAndSend(
"users-exchange",
"user.created",
json
);
}
}
// Subscriber (в другом микросервисе)
@Component
public class UserEventListener {
@RabbitListener(queues = "orders-queue")
public void onUserCreated(UserCreatedEvent event) {
// Создаём соответствующий аккаунт в Order Service
orderService.initializeUserAccount(event.getUserId());
}
}
Database Schema (Goose migration)
-- migrations/0001_create_users_table.sql
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at DESC);
-- migrations/0002_create_user_roles_table.sql
CREATE TABLE user_roles (
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role VARCHAR(50) NOT NULL,
PRIMARY KEY (user_id, role)
);
Configuration & Dependency Injection
@Configuration
public class UsersModuleConfig {
@Bean
public CreateUserUseCase createUserUseCase(
UserRepository userRepository,
PasswordHashingService passwordHashingService,
UserEventPublisher eventPublisher,
UserValidator validator
) {
return new CreateUserUseCase(
userRepository,
passwordHashingService,
eventPublisher,
validator
);
}
@Bean
public UserValidator userValidator() {
return new UserValidator();
}
@Bean
public PasswordHashingService passwordHashingService() {
return new Argon2PasswordHashingService();
}
}
Testing Strategy
// Unit test Domain Layer
@Test
public void testUserCanBeCreated() {
User user = User.create("john@example.com", "John", "password");
assertThat(user.getId()).isNotNull();
assertThat(user.getEmail().value()).isEqualTo("john@example.com");
assertThat(user.getDomainEvents()).hasSize(1);
}
// Integration test UseCase
@SpringBootTest
public class CreateUserUseCaseIntegrationTest {
@Autowired
private CreateUserUseCase useCase;
@Autowired
private UserRepository repository;
@Test
@Transactional
public void testCreateUserPersistsData() {
CreateUserRequest request = new CreateUserRequest(
"jane@example.com",
"Jane",
"password123"
);
UserResponse response = useCase.execute(request);
User savedUser = repository.findById(response.getId()).orElseThrow();
assertThat(savedUser.getEmail().value()).isEqualTo("jane@example.com");
}
}
// E2E test REST API
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerE2ETest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testCreateUserEndpoint() {
CreateUserRequest request = new CreateUserRequest(
"test@example.com",
"Test User",
"password123"
);
ResponseEntity<UserResponse> response = restTemplate.postForEntity(
"/api/v1/users",
request,
UserResponse.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody().getId()).isNotNull();
}
}
Monitoring & Observability
# application.yml
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
environment: ${app.environment}
tracing:
sampling:
probability: 0.1 # 10% трейсинга
logging:
level:
root: INFO
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
Key Design Principles
✅ DDD (Domain-Driven Design)
- Domain Entity со своей бизнес-логикой
- Domain Events для асинхронной коммуникации
- Repository interface в domain, реализация в infrastructure
✅ Clean Architecture
- Четкие слои: Presentation → Application → Domain → Infrastructure
- Зависимости направлены внутрь (Presentation → Domain)
- Никакие слои не знают о деталях внешних слоев
✅ SOLID
- Single Responsibility: каждый класс одна задача
- Open/Closed: открыт для расширения, закрыт для модификации
- Liskov Substitution: полиморфизм через интерфейсы
- Interface Segregation: узкие интерфейсы
- Dependency Inversion: зависит от интерфейсов, не реализаций
✅ Микросервисная архитектура
- Независимые базы данных (Database per service)
- Асинхронная коммуникация через message broker
- Event-driven для развязывания сервисов
✅ Testing Strategy
- Unit тесты для Domain Layer (быстрые)
- Integration тесты для Application Layer
- E2E тесты для API (Testcontainers для БД)
✅ Production-Ready
- Logging: ELK Stack
- Monitoring: Prometheus + Grafana
- Tracing: Jaeger
- Health checks: Spring Actuator
Итог
Мой последний проект демонстрирует:
- DDD & Clean Architecture — правильное разделение ответственности
- Микросервисы — независимые, масштабируемые сервисы
- Event-Driven — слабая связанность через события
- Production-Ready — мониторинг, логирование, трейсинг
- TDD & Coverage — тесты на всех уровнях (90%+)
- Goose Migrations — версионированные миграции БД
- Security — Spring Security, Password Hashing, API Gateway