From 0737a87447ea17b31e31606d064949822e75fb0d Mon Sep 17 00:00:00 2001 From: Pol Pinol Castuera Date: Sat, 31 May 2025 10:19:12 +0200 Subject: [PATCH 1/4] Use sonar cloud and linter --- .github/workflows/pull-request-pipeline.yml | 5 +- HELP.md | 1 - build.gradle | 74 ++++++++++++------- .../RegisterUserCommand.java | 7 +- .../RegisterUserCommandHandler.java | 26 ++++--- .../RequestUserById/GetUserByIdQuery.java | 3 +- .../GetUserByIdQueryHandler.java | 18 ++--- .../RequestUserUseCase/GetUserQuery.java | 3 +- .../GetUserQueryHandler.java | 14 ++-- .../UpdateUserUseCase/UpdateUserCommand.java | 6 +- .../UpdateUserCommandHandler.java | 31 ++++---- .../UpdateUserUseCase/UserDoesntExist.java | 3 +- .../io/autoinvestor/application/UserDTO.java | 8 +- .../application/UsersReadModel.java | 4 +- .../io/autoinvestor/domain/EventStore.java | 2 +- src/main/java/io/autoinvestor/domain/Id.java | 6 +- .../io/autoinvestor/domain/events/Event.java | 3 +- .../domain/events/EventSourcedEntity.java | 1 - .../autoinvestor/domain/model/FirstName.java | 6 +- .../domain/model/InvalidPasswordLength.java | 9 ++- .../autoinvestor/domain/model/LastName.java | 6 +- .../autoinvestor/domain/model/RiskLevel.java | 3 +- .../io/autoinvestor/domain/model/User.java | 23 +++--- .../autoinvestor/domain/model/UserEmail.java | 7 +- .../autoinvestor/domain/model/UserState.java | 15 +++- .../domain/model/UserWasRegisteredEvent.java | 31 ++++---- .../model/UserWasRegisteredEventPayload.java | 16 ++-- .../domain/model/UserWasUpdatedEvent.java | 19 +++-- .../model/UserWasUpdatedEventPayload.java | 8 +- .../event_publishers/EventMessageMapper.java | 16 ++-- .../InMemoryEventPublisher.java | 7 +- .../PubsubEventPublisher.java | 21 +++--- .../read_models/InMemoryUsersReadModel.java | 15 ++-- .../read_models/MongoUsersReadModel.java | 14 ++-- .../read_models/UserDocument.java | 8 +- .../read_models/UserMapper.java | 16 +--- .../repositories/EventDocument.java | 50 ++++++------- .../repositories/EventMapper.java | 15 ++-- .../InMemoryEventStoreRepository.java | 18 ++--- .../MongoEventStoreRepository.java | 30 ++++---- .../autoinvestor/ui/ErrorResponseBuilder.java | 3 +- .../io/autoinvestor/ui/GetUserController.java | 9 ++- .../ui/GlobalExceptionHandler.java | 53 ++++++++++--- .../ui/RegisterUserController.java | 17 ++--- .../autoinvestor/ui/RegisterUserRequest.java | 7 +- .../autoinvestor/ui/UpdateUserController.java | 7 +- .../io/autoinvestor/ui/UpdateUserDTO.java | 4 +- .../java/io/autoinvestor/ui/UserResponse.java | 8 +- 48 files changed, 340 insertions(+), 336 deletions(-) diff --git a/.github/workflows/pull-request-pipeline.yml b/.github/workflows/pull-request-pipeline.yml index 05665e0..bc171bd 100644 --- a/.github/workflows/pull-request-pipeline.yml +++ b/.github/workflows/pull-request-pipeline.yml @@ -20,4 +20,7 @@ jobs: java-version: 21 - name: Build & Test - run: ./gradlew clean build \ No newline at end of file + run: ./gradlew clean build + + - name: Spotless Check + run: ./gradlew spotlessCheck \ No newline at end of file diff --git a/HELP.md b/HELP.md index f876abc..bdbf568 100644 --- a/HELP.md +++ b/HELP.md @@ -11,4 +11,3 @@ For further reference, please consider the following sections: These additional references should also help you: * [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) - diff --git a/build.gradle b/build.gradle index 04e753c..edebe95 100644 --- a/build.gradle +++ b/build.gradle @@ -1,51 +1,71 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.4.3' - id 'io.spring.dependency-management' version '1.1.7' - id 'io.freefair.lombok' version '8.13.1' + id 'java' + id 'org.springframework.boot' version '3.4.3' + id 'io.spring.dependency-management' version '1.1.7' + id 'io.freefair.lombok' version '8.13.1' + id 'com.diffplug.spotless' version '7.0.4' } group = 'io.autoinvestor' java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'com.google.cloud:google-cloud-pubsub:1.123.0' - implementation "com.google.cloud:spring-cloud-gcp-starter-pubsub:6.1.1" - implementation 'com.fasterxml.jackson.core:jackson-databind' - implementation 'org.springframework.integration:spring-integration-core' - implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + implementation 'com.google.cloud:google-cloud-pubsub:1.123.0' + implementation "com.google.cloud:spring-cloud-gcp-starter-pubsub:6.1.1" + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'org.springframework.integration:spring-integration-core' + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' - testImplementation 'org.springframework.boot:spring-boot-testcontainers' - testImplementation 'org.testcontainers:testcontainers' - testImplementation 'org.testcontainers:junit-jupiter' - testImplementation 'org.testcontainers:gcloud' + testImplementation 'org.springframework.boot:spring-boot-testcontainers' + testImplementation 'org.testcontainers:testcontainers' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:gcloud' - compileOnly 'org.projectlombok:lombok:1.18.38' - annotationProcessor 'org.projectlombok:lombok:1.18.38' + compileOnly 'org.projectlombok:lombok:1.18.38' + annotationProcessor 'org.projectlombok:lombok:1.18.38' - testCompileOnly 'org.projectlombok:lombok:1.18.38' - testAnnotationProcessor 'org.projectlombok:lombok:1.18.38' + testCompileOnly 'org.projectlombok:lombok:1.18.38' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.38' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } tasks.withType(JavaCompile).configureEach { - options.compilerArgs << '-parameters' + options.compilerArgs << '-parameters' } bootBuildImage { - publish = false + publish = false +} + +spotless { + java { + googleJavaFormat('1.22.0').aosp() + removeUnusedImports() + trimTrailingWhitespace() + leadingTabsToSpaces() + endWithNewline() + importOrder '', 'java', 'javax', 'org', 'com' + target 'src/**/*.java' + } + + format 'misc', { + target '*.gradle', '*.md', '.gitignore' + leadingTabsToSpaces() + trimTrailingWhitespace() + endWithNewline() + } } diff --git a/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommand.java b/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommand.java index 6f9989a..9430374 100644 --- a/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommand.java +++ b/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommand.java @@ -3,9 +3,4 @@ import jakarta.annotation.Nullable; public record RegisterUserCommand( - @Nullable String firstName, - @Nullable String lastName, - String email, - Integer riskLevel -) { -} + @Nullable String firstName, @Nullable String lastName, String email, Integer riskLevel) {} diff --git a/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommandHandler.java b/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommandHandler.java index 127065e..1c1848f 100644 --- a/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommandHandler.java +++ b/src/main/java/io/autoinvestor/application/RegisterUserUseCase/RegisterUserCommandHandler.java @@ -2,14 +2,15 @@ import io.autoinvestor.application.UserDTO; import io.autoinvestor.application.UsersReadModel; +import io.autoinvestor.domain.EventStore; import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.events.EventPublisher; -import io.autoinvestor.domain.EventStore; import io.autoinvestor.domain.model.User; import io.autoinvestor.exceptions.BadRequestException; +import lombok.RequiredArgsConstructor; + import java.util.List; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service @@ -29,19 +30,24 @@ public void handle(RegisterUserCommand command) { throw UserRegisteredAlreadyExists.with(command.email()); } - User user = User.create(command.firstName(), command.lastName(), command.email(), command.riskLevel()); + User user = + User.create( + command.firstName(), + command.lastName(), + command.email(), + command.riskLevel()); List> events = user.getUncommittedEvents(); this.eventStore.save(user); - UserDTO dto = new UserDTO( - user.getState().userId().value(), - command.email(), - command.firstName(), - command.lastName(), - command.riskLevel() - ); + UserDTO dto = + new UserDTO( + user.getState().userId().value(), + command.email(), + command.firstName(), + command.lastName(), + command.riskLevel()); this.readModel.save(dto); this.eventPublisher.publish(events); diff --git a/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQuery.java b/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQuery.java index 0aacafe..2c16afb 100644 --- a/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQuery.java +++ b/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQuery.java @@ -1,4 +1,3 @@ package io.autoinvestor.application.RequestUserById; -public record GetUserByIdQuery(String userId) { -} +public record GetUserByIdQuery(String userId) {} diff --git a/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQueryHandler.java b/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQueryHandler.java index 971f308..b559bfb 100644 --- a/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQueryHandler.java +++ b/src/main/java/io/autoinvestor/application/RequestUserById/GetUserByIdQueryHandler.java @@ -1,10 +1,11 @@ package io.autoinvestor.application.RequestUserById; -import io.autoinvestor.application.UserNotFound; import io.autoinvestor.application.UserDTO; +import io.autoinvestor.application.UserNotFound; import io.autoinvestor.application.UsersReadModel; import io.autoinvestor.ui.UserResponse; import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Service; @Service @@ -12,16 +13,13 @@ public class GetUserByIdQueryHandler { private final UsersReadModel readModel; - public UserResponse handle (GetUserByIdQuery query) { - UserDTO dto = this.readModel.getById(query.userId()) - .orElseThrow(() -> UserNotFound.with(query.userId())); + public UserResponse handle(GetUserByIdQuery query) { + UserDTO dto = + this.readModel + .getById(query.userId()) + .orElseThrow(() -> UserNotFound.with(query.userId())); return new UserResponse( - dto.userId(), - dto.firstName(), - dto.lastName(), - dto.email(), - dto.riskLevel() - ); + dto.userId(), dto.firstName(), dto.lastName(), dto.email(), dto.riskLevel()); } } diff --git a/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQuery.java b/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQuery.java index 0e640eb..788546a 100644 --- a/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQuery.java +++ b/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQuery.java @@ -1,4 +1,3 @@ package io.autoinvestor.application.RequestUserUseCase; -public record GetUserQuery(String email) { -} +public record GetUserQuery(String email) {} diff --git a/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQueryHandler.java b/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQueryHandler.java index 1fbc616..9128dc9 100644 --- a/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQueryHandler.java +++ b/src/main/java/io/autoinvestor/application/RequestUserUseCase/GetUserQueryHandler.java @@ -5,6 +5,7 @@ import io.autoinvestor.application.UsersReadModel; import io.autoinvestor.ui.UserResponse; import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Service; @Service @@ -14,15 +15,12 @@ public class GetUserQueryHandler { private final UsersReadModel usersReadModel; public UserResponse handle(GetUserQuery query) { - UserDTO dto = this.usersReadModel.get(query.email()) - .orElseThrow(() -> UserNotFound.with(query.email())); + UserDTO dto = + this.usersReadModel + .get(query.email()) + .orElseThrow(() -> UserNotFound.with(query.email())); return new UserResponse( - dto.userId(), - dto.firstName(), - dto.lastName(), - dto.email(), - dto.riskLevel() - ); + dto.userId(), dto.firstName(), dto.lastName(), dto.email(), dto.riskLevel()); } } diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java index 37f587f..a8f344f 100644 --- a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java @@ -1,7 +1,3 @@ package io.autoinvestor.application.UpdateUserUseCase; -public record UpdateUserCommand( - String userId, - int riskLevel -) { -} +public record UpdateUserCommand(String userId, int riskLevel) {} diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java index e7f6e1f..acb74fb 100644 --- a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java @@ -9,10 +9,10 @@ import io.autoinvestor.domain.model.User; import io.autoinvestor.domain.model.UserId; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; import java.util.List; -import java.util.Optional; + +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor @@ -22,12 +22,14 @@ public class UpdateUserCommandHandler { private final EventPublisher eventPublisher; private final UsersReadModel readModel; + public void handle(UpdateUserCommand command) { - public void handle (UpdateUserCommand command) { - - String userIdToUpdate = String.valueOf(readModel.getById(command.userId()) - .map(UserDTO::riskLevel) - .orElseThrow(() -> UserNotFound.with(command.userId()))); + String userIdToUpdate = + String.valueOf( + readModel + .getById(command.userId()) + .map(UserDTO::riskLevel) + .orElseThrow(() -> UserNotFound.with(command.userId()))); User user = this.eventStore.get(UserId.from(userIdToUpdate)); user.update(userIdToUpdate, command.riskLevel()); @@ -35,18 +37,17 @@ public void handle (UpdateUserCommand command) { this.eventStore.save(user); - UserDTO dto = new UserDTO( - user.getState().userId().value(), - user.getState().userEmail().value(), - user.getState().firstName().value(), - user.getState().lastName().value(), - command.riskLevel() - ); + UserDTO dto = + new UserDTO( + user.getState().userId().value(), + user.getState().userEmail().value(), + user.getState().firstName().value(), + user.getState().lastName().value(), + command.riskLevel()); this.readModel.update(dto); this.eventPublisher.publish(events); user.markEventsAsCommitted(); - } } diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java index c6aea32..0d72cf5 100644 --- a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java @@ -6,8 +6,7 @@ private UserDoesntExist(String message) { } public static UserDoesntExist with(String userId) { - String message = "User with id " + userId + "doesn't exist"; + String message = "User with id " + userId + "doesn't exist"; return new UserDoesntExist(message); } - } diff --git a/src/main/java/io/autoinvestor/application/UserDTO.java b/src/main/java/io/autoinvestor/application/UserDTO.java index 572700f..8549edc 100644 --- a/src/main/java/io/autoinvestor/application/UserDTO.java +++ b/src/main/java/io/autoinvestor/application/UserDTO.java @@ -1,8 +1,4 @@ package io.autoinvestor.application; -public record UserDTO(String userId, - String email, - String firstName, - String lastName, - int riskLevel) { -} +public record UserDTO( + String userId, String email, String firstName, String lastName, int riskLevel) {} diff --git a/src/main/java/io/autoinvestor/application/UsersReadModel.java b/src/main/java/io/autoinvestor/application/UsersReadModel.java index 28a70b6..855d063 100644 --- a/src/main/java/io/autoinvestor/application/UsersReadModel.java +++ b/src/main/java/io/autoinvestor/application/UsersReadModel.java @@ -1,11 +1,13 @@ package io.autoinvestor.application; - import java.util.Optional; public interface UsersReadModel { void save(UserDTO user); + Optional get(String email); + Optional getById(String userId); + void update(UserDTO dto); } diff --git a/src/main/java/io/autoinvestor/domain/EventStore.java b/src/main/java/io/autoinvestor/domain/EventStore.java index 726af11..c59145e 100644 --- a/src/main/java/io/autoinvestor/domain/EventStore.java +++ b/src/main/java/io/autoinvestor/domain/EventStore.java @@ -3,8 +3,8 @@ import io.autoinvestor.domain.model.User; import io.autoinvestor.domain.model.UserId; - public interface EventStore { void save(User user); + User get(UserId userId); } diff --git a/src/main/java/io/autoinvestor/domain/Id.java b/src/main/java/io/autoinvestor/domain/Id.java index ea90fdb..d79907b 100644 --- a/src/main/java/io/autoinvestor/domain/Id.java +++ b/src/main/java/io/autoinvestor/domain/Id.java @@ -20,10 +20,8 @@ protected static String generateId() { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof Id that)) - return false; + if (this == o) return true; + if (!(o instanceof Id that)) return false; return Objects.equals(id, that.id); } diff --git a/src/main/java/io/autoinvestor/domain/events/Event.java b/src/main/java/io/autoinvestor/domain/events/Event.java index 940207c..fe6725e 100644 --- a/src/main/java/io/autoinvestor/domain/events/Event.java +++ b/src/main/java/io/autoinvestor/domain/events/Event.java @@ -27,7 +27,8 @@ protected Event(Id aggregateId, String type, P payload, int version) { this.version = version; } - protected Event(EventId id, Id aggregateId, String type, P payload, Date occurredAt, int version) { + protected Event( + EventId id, Id aggregateId, String type, P payload, Date occurredAt, int version) { this.id = id; this.aggregateId = aggregateId; this.type = type; diff --git a/src/main/java/io/autoinvestor/domain/events/EventSourcedEntity.java b/src/main/java/io/autoinvestor/domain/events/EventSourcedEntity.java index abfef71..c592e73 100644 --- a/src/main/java/io/autoinvestor/domain/events/EventSourcedEntity.java +++ b/src/main/java/io/autoinvestor/domain/events/EventSourcedEntity.java @@ -33,4 +33,3 @@ public void markEventsAsCommitted() { appliedEvents.clear(); } } - diff --git a/src/main/java/io/autoinvestor/domain/model/FirstName.java b/src/main/java/io/autoinvestor/domain/model/FirstName.java index 2e7ebb7..e0c47b6 100644 --- a/src/main/java/io/autoinvestor/domain/model/FirstName.java +++ b/src/main/java/io/autoinvestor/domain/model/FirstName.java @@ -25,10 +25,8 @@ public String value() { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof FirstName that)) - return false; + if (this == o) return true; + if (!(o instanceof FirstName that)) return false; return firstName.equals(that.firstName); } } diff --git a/src/main/java/io/autoinvestor/domain/model/InvalidPasswordLength.java b/src/main/java/io/autoinvestor/domain/model/InvalidPasswordLength.java index deb824b..65303e0 100644 --- a/src/main/java/io/autoinvestor/domain/model/InvalidPasswordLength.java +++ b/src/main/java/io/autoinvestor/domain/model/InvalidPasswordLength.java @@ -6,8 +6,13 @@ private InvalidPasswordLength(String message) { } public static InvalidPasswordLength with(Integer length, Integer minLength) { - String exceptionMessage = "Your current password length of " + length + " is invalid. It " - + "should be at least " + minLength + " characters long"; + String exceptionMessage = + "Your current password length of " + + length + + " is invalid. It " + + "should be at least " + + minLength + + " characters long"; return new InvalidPasswordLength(exceptionMessage); } } diff --git a/src/main/java/io/autoinvestor/domain/model/LastName.java b/src/main/java/io/autoinvestor/domain/model/LastName.java index 293ee12..f637944 100644 --- a/src/main/java/io/autoinvestor/domain/model/LastName.java +++ b/src/main/java/io/autoinvestor/domain/model/LastName.java @@ -25,10 +25,8 @@ public String value() { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof LastName that)) - return false; + if (this == o) return true; + if (!(o instanceof LastName that)) return false; return lastName.equals(that.lastName); } } diff --git a/src/main/java/io/autoinvestor/domain/model/RiskLevel.java b/src/main/java/io/autoinvestor/domain/model/RiskLevel.java index 1953b5f..b9d6410 100644 --- a/src/main/java/io/autoinvestor/domain/model/RiskLevel.java +++ b/src/main/java/io/autoinvestor/domain/model/RiskLevel.java @@ -1,8 +1,9 @@ package io.autoinvestor.domain.model; -import com.fasterxml.jackson.annotation.JsonValue; import io.autoinvestor.exceptions.RiskLevelNotValid; +import com.fasterxml.jackson.annotation.JsonValue; + public class RiskLevel { private final int riskLevel; diff --git a/src/main/java/io/autoinvestor/domain/model/User.java b/src/main/java/io/autoinvestor/domain/model/User.java index 96bc5e7..8d00cf7 100644 --- a/src/main/java/io/autoinvestor/domain/model/User.java +++ b/src/main/java/io/autoinvestor/domain/model/User.java @@ -31,29 +31,26 @@ public static User from(List> stream) { public static User create(String firstName, String lastName, String email, int riskLevel) { User user = User.empty(); - user.apply(UserWasRegisteredEvent.with( - user.getState().userId(), - FirstName.from(firstName), - LastName.from(lastName), - UserEmail.from(email), - RiskLevel.from(riskLevel), - user.version - )); + user.apply( + UserWasRegisteredEvent.with( + user.getState().userId(), + FirstName.from(firstName), + LastName.from(lastName), + UserEmail.from(email), + RiskLevel.from(riskLevel), + user.version)); return user; } public void update(String userId, int riskLevel) { - this.apply(UserWasUpdatedEvent.with( - UserId.from(userId), - RiskLevel.from(riskLevel) - )); + this.apply(UserWasUpdatedEvent.with(UserId.from(userId), RiskLevel.from(riskLevel))); } @Override protected void when(Event event) { switch (event.getType()) { - case UserWasRegisteredEvent.TYPE : + case UserWasRegisteredEvent.TYPE: whenUserCreated((UserWasRegisteredEvent) event); break; case UserWasUpdatedEvent.TYPE: diff --git a/src/main/java/io/autoinvestor/domain/model/UserEmail.java b/src/main/java/io/autoinvestor/domain/model/UserEmail.java index c3c6ed9..1616c17 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserEmail.java +++ b/src/main/java/io/autoinvestor/domain/model/UserEmail.java @@ -1,10 +1,12 @@ package io.autoinvestor.domain.model; -import com.fasterxml.jackson.annotation.JsonValue; import io.autoinvestor.exceptions.EmailNotValid; + import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.fasterxml.jackson.annotation.JsonValue; + public class UserEmail { private final String email; @@ -13,7 +15,8 @@ public class UserEmail { } private static void validate(String email) { - String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; + String emailRegex = + "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; Pattern pattern = Pattern.compile(emailRegex); Matcher matcher = pattern.matcher(email); if (!matcher.matches()) { diff --git a/src/main/java/io/autoinvestor/domain/model/UserState.java b/src/main/java/io/autoinvestor/domain/model/UserState.java index c8f65c7..2ed2afc 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserState.java +++ b/src/main/java/io/autoinvestor/domain/model/UserState.java @@ -9,15 +9,22 @@ public record UserState( UserEmail userEmail, RiskLevel riskLevel, Date createdAt, - Date updatedAt -) { + Date updatedAt) { public static UserState empty() { - return new UserState(UserId.generate(), FirstName.empty(), LastName.empty(), UserEmail.empty(), RiskLevel.empty(), new Date(), new Date()); + return new UserState( + UserId.generate(), + FirstName.empty(), + LastName.empty(), + UserEmail.empty(), + RiskLevel.empty(), + new Date(), + new Date()); } public UserState withUserCreated(UserWasRegisteredEvent event) { UserWasRegisteredEventPayload payload = event.getPayload(); - return new UserState(UserId.from(event.getAggregateId().value()), + return new UserState( + UserId.from(event.getAggregateId().value()), FirstName.from(payload.firstName()), LastName.from(payload.lastName()), UserEmail.from(payload.email()), diff --git a/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEvent.java b/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEvent.java index 3502709..2aadbd7 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEvent.java +++ b/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEvent.java @@ -1,7 +1,7 @@ package io.autoinvestor.domain.model; -import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.Id; +import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.events.EventId; import java.util.Date; @@ -10,15 +10,17 @@ public class UserWasRegisteredEvent extends Event public static final String TYPE = "USER_CREATED"; - private UserWasRegisteredEvent(Id aggregateId, UserWasRegisteredEventPayload payload, int version) { + private UserWasRegisteredEvent( + Id aggregateId, UserWasRegisteredEventPayload payload, int version) { super(aggregateId, TYPE, payload, version); } - protected UserWasRegisteredEvent(EventId id, - Id aggregateId, - UserWasRegisteredEventPayload payload, - Date occurredAt, - int version) { + protected UserWasRegisteredEvent( + EventId id, + Id aggregateId, + UserWasRegisteredEventPayload payload, + Date occurredAt, + int version) { super(id, aggregateId, TYPE, payload, occurredAt, version); } @@ -29,15 +31,18 @@ public static UserWasRegisteredEvent with( UserEmail userEmail, RiskLevel riskLevel, int version) { - var payload = new UserWasRegisteredEventPayload(firstName.value(), lastName.value(), userEmail.value(), riskLevel.value()); + var payload = + new UserWasRegisteredEventPayload( + firstName.value(), lastName.value(), userEmail.value(), riskLevel.value()); return new UserWasRegisteredEvent(userId, payload, version); } - public static UserWasRegisteredEvent hydrate(EventId id, - Id aggregateId, - UserWasRegisteredEventPayload payload, - Date occurredAt, - int version) { + public static UserWasRegisteredEvent hydrate( + EventId id, + Id aggregateId, + UserWasRegisteredEventPayload payload, + Date occurredAt, + int version) { return new UserWasRegisteredEvent(id, aggregateId, payload, occurredAt, version); } } diff --git a/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEventPayload.java b/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEventPayload.java index 20d3386..d7d1c25 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEventPayload.java +++ b/src/main/java/io/autoinvestor/domain/model/UserWasRegisteredEventPayload.java @@ -5,13 +5,17 @@ import java.util.Map; public record UserWasRegisteredEventPayload( - String firstName, - String lastName, - String email, - int riskLevel -) implements EventPayload { + String firstName, String lastName, String email, int riskLevel) implements EventPayload { @Override public Map asMap() { - return Map.of("firstName", firstName, "lastName", lastName, "email", email, "riskLevel", riskLevel); + return Map.of( + "firstName", + firstName, + "lastName", + lastName, + "email", + email, + "riskLevel", + riskLevel); } } diff --git a/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java index db65c1a..10c9e72 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java +++ b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java @@ -6,7 +6,7 @@ import java.util.Date; -public class UserWasUpdatedEvent extends Event{ +public class UserWasUpdatedEvent extends Event { public static final String TYPE = "SUBSCRIPTION_UPDATED"; private UserWasUpdatedEvent(Id aggregateId, UserWasUpdatedEventPayload payload) { @@ -22,18 +22,17 @@ protected UserWasUpdatedEvent( super(id, aggregateId, TYPE, payload, occurredAt, version); } - public static UserWasUpdatedEvent with (UserId userId, - RiskLevel riskLevel) { - UserWasUpdatedEventPayload payload = new UserWasUpdatedEventPayload( - riskLevel.value()); + public static UserWasUpdatedEvent with(UserId userId, RiskLevel riskLevel) { + UserWasUpdatedEventPayload payload = new UserWasUpdatedEventPayload(riskLevel.value()); return new UserWasUpdatedEvent(userId, payload); } - public static UserWasUpdatedEvent hydrate (EventId id, - Id aggregateId, - UserWasUpdatedEventPayload payload, - Date occurredAt, - int version){ + public static UserWasUpdatedEvent hydrate( + EventId id, + Id aggregateId, + UserWasUpdatedEventPayload payload, + Date occurredAt, + int version) { return new UserWasUpdatedEvent(id, aggregateId, payload, occurredAt, version); } } diff --git a/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java index 8b70d03..c1395e0 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java +++ b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java @@ -4,13 +4,9 @@ import java.util.Map; -public record UserWasUpdatedEventPayload( - int riskLevel -) implements EventPayload { +public record UserWasUpdatedEventPayload(int riskLevel) implements EventPayload { @Override public Map asMap() { - return Map.of( - "riskLevel", riskLevel - ); + return Map.of("riskLevel", riskLevel); } } diff --git a/src/main/java/io/autoinvestor/infrastructure/event_publishers/EventMessageMapper.java b/src/main/java/io/autoinvestor/infrastructure/event_publishers/EventMessageMapper.java index 90fc31c..4bee728 100644 --- a/src/main/java/io/autoinvestor/infrastructure/event_publishers/EventMessageMapper.java +++ b/src/main/java/io/autoinvestor/infrastructure/event_publishers/EventMessageMapper.java @@ -1,9 +1,5 @@ package io.autoinvestor.infrastructure.event_publishers; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; import io.autoinvestor.domain.events.Event; import io.autoinvestor.exceptions.InternalErrorException; @@ -11,6 +7,10 @@ import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; final class EventMessageMapper { @@ -27,14 +27,12 @@ PubsubMessage toMessage(Event event) { envelope.put("eventId", event.getId().value()); envelope.put("type", event.getType()); envelope.put("aggregateId", event.getAggregateId().value()); - envelope.put("occurredAt", - Instant.ofEpochMilli(event.getOccurredAt().getTime()).toString()); + envelope.put( + "occurredAt", Instant.ofEpochMilli(event.getOccurredAt().getTime()).toString()); envelope.put("version", event.getVersion()); String json = objectMapper.writeValueAsString(envelope); - return PubsubMessage.newBuilder() - .setData(ByteString.copyFromUtf8(json)) - .build(); + return PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(json)).build(); } catch (JsonProcessingException ex) { throw new InternalErrorException("Failed to serialise domain event"); } diff --git a/src/main/java/io/autoinvestor/infrastructure/event_publishers/InMemoryEventPublisher.java b/src/main/java/io/autoinvestor/infrastructure/event_publishers/InMemoryEventPublisher.java index edbe2f1..f2e4760 100644 --- a/src/main/java/io/autoinvestor/infrastructure/event_publishers/InMemoryEventPublisher.java +++ b/src/main/java/io/autoinvestor/infrastructure/event_publishers/InMemoryEventPublisher.java @@ -2,8 +2,10 @@ import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.events.EventPublisher; + import java.util.ArrayList; import java.util.List; + import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -27,6 +29,9 @@ public void publish(List> events) { public boolean hasPublishedEvent(String type, String aggregateId) { return publishedEvents.stream() - .anyMatch(event -> event.getType().equals(type) && event.getAggregateId().value().equals(aggregateId)); + .anyMatch( + event -> + event.getType().equals(type) + && event.getAggregateId().value().equals(aggregateId)); } } diff --git a/src/main/java/io/autoinvestor/infrastructure/event_publishers/PubsubEventPublisher.java b/src/main/java/io/autoinvestor/infrastructure/event_publishers/PubsubEventPublisher.java index ffa5296..6d0762b 100644 --- a/src/main/java/io/autoinvestor/infrastructure/event_publishers/PubsubEventPublisher.java +++ b/src/main/java/io/autoinvestor/infrastructure/event_publishers/PubsubEventPublisher.java @@ -1,18 +1,20 @@ package io.autoinvestor.infrastructure.event_publishers; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.pubsub.v1.ProjectTopicName; -import com.google.cloud.pubsub.v1.Publisher; import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.events.EventPublisher; import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.concurrent.TimeUnit; + import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; -import java.util.List; -import java.util.concurrent.TimeUnit; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.cloud.pubsub.v1.Publisher; +import com.google.pubsub.v1.ProjectTopicName; @Slf4j @Component @@ -25,8 +27,8 @@ public class PubsubEventPublisher implements EventPublisher { public PubsubEventPublisher( @Value("${GCP_PROJECT}") String projectId, @Value("${PUBSUB_TOPIC}") String topic, - ObjectMapper objectMapper - ) throws Exception { + ObjectMapper objectMapper) + throws Exception { this.mapper = new EventMessageMapper(objectMapper); ProjectTopicName topicName = ProjectTopicName.of(projectId, topic); this.publisher = Publisher.newBuilder(topicName).build(); @@ -43,9 +45,7 @@ public void publish(List> events) { log.info("Publishing {} domain event(s)", events.size()); - events.stream() - .map(mapper::toMessage) - .forEach(publisher::publish); + events.stream().map(mapper::toMessage).forEach(publisher::publish); } @PreDestroy @@ -55,4 +55,3 @@ public void shutdown() throws Exception { publisher.awaitTermination(1, TimeUnit.MINUTES); } } - diff --git a/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java b/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java index 6d4d93b..45c8486 100644 --- a/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java +++ b/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java @@ -2,12 +2,13 @@ import io.autoinvestor.application.UserDTO; import io.autoinvestor.application.UsersReadModel; -import org.springframework.stereotype.Repository; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.springframework.stereotype.Repository; + @Repository public class InMemoryUsersReadModel implements UsersReadModel { @@ -20,20 +21,14 @@ public void save(UserDTO dto) { @Override public Optional get(String email) { - return users.stream() - .filter(user -> user.email().equals(email)) - .findFirst(); + return users.stream().filter(user -> user.email().equals(email)).findFirst(); } @Override public Optional getById(String userId) { - return users.stream() - .filter(user -> user.userId().equals(userId)) - .findFirst(); + return users.stream().filter(user -> user.userId().equals(userId)).findFirst(); } @Override - public void update(UserDTO dto) { - - } + public void update(UserDTO dto) {} } diff --git a/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java b/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java index af1d447..7cec84a 100644 --- a/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java +++ b/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java @@ -2,6 +2,9 @@ import io.autoinvestor.application.UserDTO; import io.autoinvestor.application.UsersReadModel; + +import java.util.Optional; + import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.data.mongodb.core.MongoTemplate; @@ -10,8 +13,6 @@ import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository @Primary @Profile("prod") @@ -36,23 +37,20 @@ public void save(UserDTO dto) { public Optional get(String email) { Query query = new Query(Criteria.where("email").is(email)); UserDocument doc = template.findOne(query, UserDocument.class, COLLECTION); - return Optional.ofNullable(doc) - .map(mapper::toDTO); + return Optional.ofNullable(doc).map(mapper::toDTO); } @Override public Optional getById(String userId) { Query query = new Query(Criteria.where("_id").is(userId)); UserDocument doc = template.findOne(query, UserDocument.class, COLLECTION); - return Optional.ofNullable(doc) - .map(mapper::toDTO); + return Optional.ofNullable(doc).map(mapper::toDTO); } @Override public void update(UserDTO dto) { Query query = new Query(Criteria.where("userId").is(dto.userId())); - Update update = new Update() - .set("riskLevel", dto.riskLevel()); + Update update = new Update().set("riskLevel", dto.riskLevel()); this.template.updateFirst(query, update, UserDocument.class, COLLECTION); } } diff --git a/src/main/java/io/autoinvestor/infrastructure/read_models/UserDocument.java b/src/main/java/io/autoinvestor/infrastructure/read_models/UserDocument.java index 5cf2780..f10dceb 100644 --- a/src/main/java/io/autoinvestor/infrastructure/read_models/UserDocument.java +++ b/src/main/java/io/autoinvestor/infrastructure/read_models/UserDocument.java @@ -5,10 +5,4 @@ @Document(collection = "users") public record UserDocument( - @Id String userId, - String email, - String firstName, - String lastName, - Integer riskLevel -) { -} + @Id String userId, String email, String firstName, String lastName, Integer riskLevel) {} diff --git a/src/main/java/io/autoinvestor/infrastructure/read_models/UserMapper.java b/src/main/java/io/autoinvestor/infrastructure/read_models/UserMapper.java index 93196fb..7d1df06 100644 --- a/src/main/java/io/autoinvestor/infrastructure/read_models/UserMapper.java +++ b/src/main/java/io/autoinvestor/infrastructure/read_models/UserMapper.java @@ -1,29 +1,19 @@ package io.autoinvestor.infrastructure.read_models; import io.autoinvestor.application.UserDTO; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Component; @Component public class UserMapper { public UserDocument toDocument(UserDTO dto) { return new UserDocument( - dto.userId(), - dto.email(), - dto.firstName(), - dto.lastName(), - dto.riskLevel() - ); + dto.userId(), dto.email(), dto.firstName(), dto.lastName(), dto.riskLevel()); } public UserDTO toDTO(UserDocument doc) { return new UserDTO( - doc.userId(), - doc.email(), - doc.firstName(), - doc.lastName(), - doc.riskLevel() - ); + doc.userId(), doc.email(), doc.firstName(), doc.lastName(), doc.riskLevel()); } } diff --git a/src/main/java/io/autoinvestor/infrastructure/repositories/EventDocument.java b/src/main/java/io/autoinvestor/infrastructure/repositories/EventDocument.java index 8db9923..dbc737a 100644 --- a/src/main/java/io/autoinvestor/infrastructure/repositories/EventDocument.java +++ b/src/main/java/io/autoinvestor/infrastructure/repositories/EventDocument.java @@ -2,49 +2,45 @@ import lombok.Getter; import lombok.Setter; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.data.mongodb.core.mapping.Field; import java.util.Date; import java.util.Map; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + @Getter @Setter @Document(collection = "events") public class EventDocument { - @Id - private String id; + @Id private String id; - @Field - private String aggregateId; + @Field private String aggregateId; - @Field - private String type; + @Field private String type; - @Field - private Map payload; + @Field private Map payload; - @Field - private Date occurredAt; + @Field private Date occurredAt; - @Field - private int version; + @Field private int version; - public EventDocument() { } + public EventDocument() {} - public EventDocument(String id, - String aggregateId, - String type, - Map payload, - Date occurredAt, - int version) { - this.id = id; + public EventDocument( + String id, + String aggregateId, + String type, + Map payload, + Date occurredAt, + int version) { + this.id = id; this.aggregateId = aggregateId; - this.type = type; - this.payload = payload; - this.occurredAt = occurredAt; - this.version = version; + this.type = type; + this.payload = payload; + this.occurredAt = occurredAt; + this.version = version; } } diff --git a/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java b/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java index 29fc695..b421b37 100644 --- a/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java +++ b/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java @@ -1,18 +1,20 @@ package io.autoinvestor.infrastructure.repositories; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.events.EventId; import io.autoinvestor.domain.events.EventPayload; import io.autoinvestor.domain.model.UserId; import io.autoinvestor.domain.model.UserWasRegisteredEvent; import io.autoinvestor.domain.model.UserWasRegisteredEventPayload; -import org.springframework.stereotype.Component; import java.util.Date; import java.util.Map; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + @Component public class EventMapper { @@ -28,8 +30,7 @@ public

EventDocument toDocument(Event

evt) { evt.getType(), payloadMap, evt.getOccurredAt(), - evt.getVersion() - ); + evt.getVersion()); } public Event toDomain(EventDocument doc) { @@ -46,9 +47,7 @@ public Event toDomain(EventDocument doc) { return UserWasRegisteredEvent.hydrate(id, aggId, payload, occurred, version); } - default -> throw new IllegalArgumentException( - "Unknown event type: " + doc.getType() - ); + default -> throw new IllegalArgumentException("Unknown event type: " + doc.getType()); } } } diff --git a/src/main/java/io/autoinvestor/infrastructure/repositories/InMemoryEventStoreRepository.java b/src/main/java/io/autoinvestor/infrastructure/repositories/InMemoryEventStoreRepository.java index c4afa4b..be38013 100644 --- a/src/main/java/io/autoinvestor/infrastructure/repositories/InMemoryEventStoreRepository.java +++ b/src/main/java/io/autoinvestor/infrastructure/repositories/InMemoryEventStoreRepository.java @@ -1,17 +1,14 @@ package io.autoinvestor.infrastructure.repositories; -import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.EventStore; +import io.autoinvestor.domain.events.Event; +import io.autoinvestor.domain.model.User; +import io.autoinvestor.domain.model.UserId; + import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import io.autoinvestor.domain.model.User; -import io.autoinvestor.domain.model.UserId; import org.springframework.context.annotation.Profile; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; @Repository @@ -34,9 +31,10 @@ public User get(UserId userId) { return null; } - List> userEvents = eventStore.stream() - .filter(event -> event.getAggregateId().value().equals(userId.value())) - .toList(); + List> userEvents = + eventStore.stream() + .filter(event -> event.getAggregateId().value().equals(userId.value())) + .toList(); return User.from(userEvents); } diff --git a/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java b/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java index 50e50b3..236496d 100644 --- a/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java +++ b/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java @@ -1,9 +1,13 @@ package io.autoinvestor.infrastructure.repositories; -import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.EventStore; +import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.model.User; import io.autoinvestor.domain.model.UserId; + +import java.util.List; +import java.util.stream.Collectors; + import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.Sort; @@ -12,10 +16,6 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.stream.Collectors; - - @Repository @Primary @Profile("prod") @@ -32,20 +32,18 @@ public MongoEventStoreRepository(MongoTemplate template, EventMapper mapper) { @Override public void save(User user) { - List docs = user.getUncommittedEvents() - .stream() - .map(mapper::toDocument) - .collect(Collectors.toList()); + List docs = + user.getUncommittedEvents().stream() + .map(mapper::toDocument) + .collect(Collectors.toList()); template.insertAll(docs); } @Override public User get(UserId userId) { - Query q = Query.query( - Criteria.where("aggregateId") - .is(userId.value()) - ) - .with(Sort.by("version")); + Query q = + Query.query(Criteria.where("aggregateId").is(userId.value())) + .with(Sort.by("version")); List docs = template.find(q, EventDocument.class, COLLECTION); @@ -53,9 +51,7 @@ public User get(UserId userId) { return null; } - List> events = docs.stream() - .map(mapper::toDomain) - .collect(Collectors.toList()); + List> events = docs.stream().map(mapper::toDomain).collect(Collectors.toList()); return User.from(events); } diff --git a/src/main/java/io/autoinvestor/ui/ErrorResponseBuilder.java b/src/main/java/io/autoinvestor/ui/ErrorResponseBuilder.java index d0bc6d1..b6a8d2b 100644 --- a/src/main/java/io/autoinvestor/ui/ErrorResponseBuilder.java +++ b/src/main/java/io/autoinvestor/ui/ErrorResponseBuilder.java @@ -7,8 +7,7 @@ public class ErrorResponseBuilder { private int status = HttpStatus.INTERNAL_SERVER_ERROR.value(); private String message = "An unexpected error occurred"; - ErrorResponseBuilder() { - } + ErrorResponseBuilder() {} public ErrorResponseBuilder status(HttpStatus status) { this.status = status.value(); diff --git a/src/main/java/io/autoinvestor/ui/GetUserController.java b/src/main/java/io/autoinvestor/ui/GetUserController.java index 1f85bdf..a270faf 100644 --- a/src/main/java/io/autoinvestor/ui/GetUserController.java +++ b/src/main/java/io/autoinvestor/ui/GetUserController.java @@ -1,10 +1,11 @@ package io.autoinvestor.ui; -import io.autoinvestor.application.RequestUserById.GetUserByIdQueryHandler; import io.autoinvestor.application.RequestUserById.GetUserByIdQuery; +import io.autoinvestor.application.RequestUserById.GetUserByIdQueryHandler; import io.autoinvestor.application.RequestUserUseCase.GetUserQuery; import io.autoinvestor.application.RequestUserUseCase.GetUserQueryHandler; import lombok.RequiredArgsConstructor; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -19,11 +20,11 @@ public class GetUserController { @GetMapping public ResponseEntity getUser( @RequestHeader(value = "X-User-Id", required = false) String userId, - @RequestParam(value = "email", required = false) String email - ) { + @RequestParam(value = "email", required = false) String email) { if (userId != null) { return ResponseEntity.ok(getUserByIdQueryHandler.handle(new GetUserByIdQuery(userId))); - } if (email != null) { + } + if (email != null) { return ResponseEntity.ok(getUserCommandHandler.handle(new GetUserQuery(email))); } return ResponseEntity.badRequest().build(); diff --git a/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java b/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java index ff44298..e2c61c9 100644 --- a/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java +++ b/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java @@ -4,12 +4,18 @@ import io.autoinvestor.application.UserNotFound; import io.autoinvestor.domain.model.InvalidPasswordLength; import io.autoinvestor.exceptions.*; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -@RestControllerAdvice(assignableTypes = {RegisterUserController.class, GetUserController.class, UpdateUserController.class}) +@RestControllerAdvice( + assignableTypes = { + RegisterUserController.class, + GetUserController.class, + UpdateUserController.class + }) public class GlobalExceptionHandler { @ExceptionHandler(DuplicatedException.class) @@ -24,43 +30,66 @@ public ResponseEntity handleConflictException(ConflictException e @ExceptionHandler(BadRequestException.class) public ResponseEntity handleBadRequestException(BadRequestException ex) { - return ErrorResponse.builder().status(HttpStatus.BAD_REQUEST).message(ex.getMessage()).build(); + return ErrorResponse.builder() + .status(HttpStatus.BAD_REQUEST) + .message(ex.getMessage()) + .build(); } @ExceptionHandler(NotFoundException.class) public ResponseEntity handleNotFoundException(NotFoundException ex) { - return ErrorResponse.builder().status(HttpStatus.NOT_FOUND).message(ex.getMessage()).build(); + return ErrorResponse.builder() + .status(HttpStatus.NOT_FOUND) + .message(ex.getMessage()) + .build(); } @ExceptionHandler(InternalErrorException.class) public ResponseEntity handleInternalErrorException(InternalErrorException ex) { - return ErrorResponse.builder().status(HttpStatus.INTERNAL_SERVER_ERROR).message(ex.getMessage()).build(); + return ErrorResponse.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .message(ex.getMessage()) + .build(); } @ExceptionHandler(EmailNotValid.class) public ResponseEntity handleEmailNotValid(EmailNotValid ex) { - return ErrorResponse.builder().status(HttpStatus.valueOf(409)).message(ex.getMessage()).build(); + return ErrorResponse.builder() + .status(HttpStatus.valueOf(409)) + .message(ex.getMessage()) + .build(); } @ExceptionHandler(UserRegisteredAlreadyExists.class) - public ResponseEntity handleUserRegisteredAlreadyExists(UserRegisteredAlreadyExists ex) { - return ErrorResponse.builder().status(HttpStatus.valueOf(409)).message(ex.getMessage()).build(); + public ResponseEntity handleUserRegisteredAlreadyExists( + UserRegisteredAlreadyExists ex) { + return ErrorResponse.builder() + .status(HttpStatus.valueOf(409)) + .message(ex.getMessage()) + .build(); } @ExceptionHandler(InvalidPasswordLength.class) public ResponseEntity handleInvalidPasswordLength(InvalidPasswordLength ex) { - return ErrorResponse.builder().status(HttpStatus.valueOf(400)).message(ex.getMessage()).build(); + return ErrorResponse.builder() + .status(HttpStatus.valueOf(400)) + .message(ex.getMessage()) + .build(); } @ExceptionHandler(RiskLevelNotValid.class) public ResponseEntity handleRiskLevelNotValid(RiskLevelNotValid ex) { - return ErrorResponse.builder().status(HttpStatus.valueOf(400)).message(ex.getMessage()).build(); + return ErrorResponse.builder() + .status(HttpStatus.valueOf(400)) + .message(ex.getMessage()) + .build(); } @ExceptionHandler(UserNotFound.class) public ResponseEntity handleUserNotFound(UserNotFound ex) { - return ErrorResponse.builder().status(HttpStatus.valueOf(404)).message(ex.getMessage()).build(); + return ErrorResponse.builder() + .status(HttpStatus.valueOf(404)) + .message(ex.getMessage()) + .build(); } - - } diff --git a/src/main/java/io/autoinvestor/ui/RegisterUserController.java b/src/main/java/io/autoinvestor/ui/RegisterUserController.java index 474e2c0..329ffa5 100644 --- a/src/main/java/io/autoinvestor/ui/RegisterUserController.java +++ b/src/main/java/io/autoinvestor/ui/RegisterUserController.java @@ -2,11 +2,8 @@ import io.autoinvestor.application.RegisterUserUseCase.RegisterUserCommand; import io.autoinvestor.application.RegisterUserUseCase.RegisterUserCommandHandler; -import io.autoinvestor.application.RequestUserById.GetUserByIdQueryHandler; -import io.autoinvestor.application.RequestUserById.GetUserByIdQuery; -import io.autoinvestor.application.RequestUserUseCase.GetUserQuery; -import io.autoinvestor.application.RequestUserUseCase.GetUserQueryHandler; import lombok.RequiredArgsConstructor; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -22,12 +19,12 @@ public class RegisterUserController { @PostMapping public ResponseEntity handle(@RequestBody RegisterUserRequest dto) { - registerUserCommandHandler.handle(new RegisterUserCommand( - dto.firstName(), - dto.lastName(), - dto.email(), - dto.riskLevel() != null ? dto.riskLevel() : DEFAULT_RISK_LEVEL - )); + registerUserCommandHandler.handle( + new RegisterUserCommand( + dto.firstName(), + dto.lastName(), + dto.email(), + dto.riskLevel() != null ? dto.riskLevel() : DEFAULT_RISK_LEVEL)); return ResponseEntity.status(HttpStatus.CREATED).build(); } } diff --git a/src/main/java/io/autoinvestor/ui/RegisterUserRequest.java b/src/main/java/io/autoinvestor/ui/RegisterUserRequest.java index 003362e..a45b5d9 100644 --- a/src/main/java/io/autoinvestor/ui/RegisterUserRequest.java +++ b/src/main/java/io/autoinvestor/ui/RegisterUserRequest.java @@ -1,9 +1,4 @@ package io.autoinvestor.ui; public record RegisterUserRequest( - String firstName, - String lastName, - String email, - Integer riskLevel -) { -} + String firstName, String lastName, String email, Integer riskLevel) {} diff --git a/src/main/java/io/autoinvestor/ui/UpdateUserController.java b/src/main/java/io/autoinvestor/ui/UpdateUserController.java index 6b9cea7..fd6f2fb 100644 --- a/src/main/java/io/autoinvestor/ui/UpdateUserController.java +++ b/src/main/java/io/autoinvestor/ui/UpdateUserController.java @@ -3,6 +3,7 @@ import io.autoinvestor.application.UpdateUserUseCase.UpdateUserCommand; import io.autoinvestor.application.UpdateUserUseCase.UpdateUserCommandHandler; import lombok.RequiredArgsConstructor; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -13,11 +14,11 @@ public class UpdateUserController { private final UpdateUserCommandHandler updateUserCommandHandler; + @PutMapping - public ResponseEntity handle ( + public ResponseEntity handle( @RequestHeader(value = "X-User-Id", required = false) String userId, - @RequestBody UpdateUserDTO dto - ){ + @RequestBody UpdateUserDTO dto) { updateUserCommandHandler.handle(new UpdateUserCommand(userId, dto.riskLevel())); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } diff --git a/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java b/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java index b36ee7c..f187863 100644 --- a/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java +++ b/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java @@ -1,5 +1,3 @@ package io.autoinvestor.ui; -public record UpdateUserDTO( - int riskLevel -) {} +public record UpdateUserDTO(int riskLevel) {} diff --git a/src/main/java/io/autoinvestor/ui/UserResponse.java b/src/main/java/io/autoinvestor/ui/UserResponse.java index 39e006f..27979c5 100644 --- a/src/main/java/io/autoinvestor/ui/UserResponse.java +++ b/src/main/java/io/autoinvestor/ui/UserResponse.java @@ -1,10 +1,4 @@ package io.autoinvestor.ui; public record UserResponse( - String userId, - String email, - String firstName, - String lastName, - int riskLevel -) { -} + String userId, String email, String firstName, String lastName, int riskLevel) {} From 78c60c04b722b98408ac5f5b6282d1d2dc5579b6 Mon Sep 17 00:00:00 2001 From: Pol Pinol Castuera Date: Sat, 31 May 2025 10:20:40 +0200 Subject: [PATCH 2/4] Apply linter --- .../UpdateUserCommandHandler.java | 31 ++++++++++--------- .../repositories/EventMapper.java | 17 +++++----- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java index 46e2048..311e700 100644 --- a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java @@ -9,10 +9,10 @@ import io.autoinvestor.domain.model.User; import io.autoinvestor.domain.model.UserId; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; import java.util.List; -import java.util.Optional; + +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor @@ -22,12 +22,14 @@ public class UpdateUserCommandHandler { private final EventPublisher eventPublisher; private final UsersReadModel readModel; + public void handle(UpdateUserCommand command) { - public void handle (UpdateUserCommand command) { - - String userIdToUpdate = String.valueOf(readModel.getById(command.userId()) - .map(UserDTO::userId) - .orElseThrow(() -> UserNotFound.with(command.userId()))); + String userIdToUpdate = + String.valueOf( + readModel + .getById(command.userId()) + .map(UserDTO::userId) + .orElseThrow(() -> UserNotFound.with(command.userId()))); User user = this.eventStore.get(UserId.from(userIdToUpdate)); user.update(userIdToUpdate, command.riskLevel()); @@ -35,18 +37,17 @@ public void handle (UpdateUserCommand command) { this.eventStore.save(user); - UserDTO dto = new UserDTO( - user.getState().userId().value(), - user.getState().userEmail().value(), - user.getState().firstName().value(), - user.getState().lastName().value(), - command.riskLevel() - ); + UserDTO dto = + new UserDTO( + user.getState().userId().value(), + user.getState().userEmail().value(), + user.getState().firstName().value(), + user.getState().lastName().value(), + command.riskLevel()); this.readModel.update(dto); this.eventPublisher.publish(events); user.markEventsAsCommitted(); - } } diff --git a/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java b/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java index 855508e..47a1c54 100644 --- a/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java +++ b/src/main/java/io/autoinvestor/infrastructure/repositories/EventMapper.java @@ -1,16 +1,18 @@ package io.autoinvestor.infrastructure.repositories; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.autoinvestor.domain.events.Event; import io.autoinvestor.domain.events.EventId; import io.autoinvestor.domain.events.EventPayload; import io.autoinvestor.domain.model.*; -import org.springframework.stereotype.Component; import java.util.Date; import java.util.Map; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + @Component public class EventMapper { @@ -26,8 +28,7 @@ public

EventDocument toDocument(Event

evt) { evt.getType(), payloadMap, evt.getOccurredAt(), - evt.getVersion() - ); + evt.getVersion()); } public Event toDomain(EventDocument doc) { @@ -43,15 +44,13 @@ public Event toDomain(EventDocument doc) { return UserWasRegisteredEvent.hydrate(id, aggId, payload, occurred, version); } - case UserWasUpdatedEvent.TYPE -> { + case UserWasUpdatedEvent.TYPE -> { UserWasUpdatedEventPayload payload = json.convertValue(doc.getPayload(), UserWasUpdatedEventPayload.class); return UserWasUpdatedEvent.hydrate(id, aggId, payload, occurred, version); } - default -> throw new IllegalArgumentException( - "Unknown event type: " + doc.getType() - ); + default -> throw new IllegalArgumentException("Unknown event type: " + doc.getType()); } } } From 256a9e69622e0934a98d63093d65f79abd3685ef Mon Sep 17 00:00:00 2001 From: Pol Pinol Castuera Date: Sat, 31 May 2025 10:39:46 +0200 Subject: [PATCH 3/4] Improve maintainability for sonar cloud --- .../io/autoinvestor/domain/model/UserEmail.java | 15 ++++++++++----- .../read_models/InMemoryUsersReadModel.java | 9 ++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/autoinvestor/domain/model/UserEmail.java b/src/main/java/io/autoinvestor/domain/model/UserEmail.java index 1616c17..5690043 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserEmail.java +++ b/src/main/java/io/autoinvestor/domain/model/UserEmail.java @@ -8,6 +8,14 @@ import com.fasterxml.jackson.annotation.JsonValue; public class UserEmail { + private static final String ATOM = "[\\w+&*-]"; + private static final String LABEL = "[A-Za-z0-9-]"; + private static final Pattern EMAIL_PATTERN = Pattern.compile( + "^" + ATOM + "{1,64}(?:\\." + ATOM + "{1,64})*@" + + LABEL + "{1,63}(?:\\." + LABEL + "{1,63})+" + + "[A-Za-z]{2,7}$" + ); + private final String email; UserEmail(String email) { @@ -15,11 +23,8 @@ public class UserEmail { } private static void validate(String email) { - String emailRegex = - "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; - Pattern pattern = Pattern.compile(emailRegex); - Matcher matcher = pattern.matcher(email); - if (!matcher.matches()) { + boolean valid = EMAIL_PATTERN.matcher(email).matches(); + if (!valid) { throw EmailNotValid.with(email); } } diff --git a/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java b/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java index 45c8486..7998438 100644 --- a/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java +++ b/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java @@ -30,5 +30,12 @@ public Optional getById(String userId) { } @Override - public void update(UserDTO dto) {} + public void update(UserDTO dto) { + for (int i = 0; i < users.size(); i++) { + if (users.get(i).userId().equals(dto.userId())) { + users.set(i, dto); + return; + } + } + } } From 65012237f04a1330847dc9227c6945b8059df7b7 Mon Sep 17 00:00:00 2001 From: Pol Pinol Castuera Date: Sat, 31 May 2025 10:43:38 +0200 Subject: [PATCH 4/4] Add linter and sonar cloud checks --- .../autoinvestor/domain/model/UserEmail.java | 22 ++++++++++++------- .../MongoEventStoreRepository.java | 4 +--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/autoinvestor/domain/model/UserEmail.java b/src/main/java/io/autoinvestor/domain/model/UserEmail.java index 5690043..29070b5 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserEmail.java +++ b/src/main/java/io/autoinvestor/domain/model/UserEmail.java @@ -2,19 +2,25 @@ import io.autoinvestor.exceptions.EmailNotValid; -import java.util.regex.Matcher; import java.util.regex.Pattern; import com.fasterxml.jackson.annotation.JsonValue; public class UserEmail { - private static final String ATOM = "[\\w+&*-]"; - private static final String LABEL = "[A-Za-z0-9-]"; - private static final Pattern EMAIL_PATTERN = Pattern.compile( - "^" + ATOM + "{1,64}(?:\\." + ATOM + "{1,64})*@" + - LABEL + "{1,63}(?:\\." + LABEL + "{1,63})+" + - "[A-Za-z]{2,7}$" - ); + private static final String ATOM = "[\\w+&*-]"; + private static final String LABEL = "[A-Za-z0-9-]"; + private static final Pattern EMAIL_PATTERN = + Pattern.compile( + "^" + + ATOM + + "{1,64}(?:\\." + + ATOM + + "{1,64})*@" + + LABEL + + "{1,63}(?:\\." + + LABEL + + "{1,63})+" + + "[A-Za-z]{2,7}$"); private final String email; diff --git a/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java b/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java index 236496d..82daac6 100644 --- a/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java +++ b/src/main/java/io/autoinvestor/infrastructure/repositories/MongoEventStoreRepository.java @@ -33,9 +33,7 @@ public MongoEventStoreRepository(MongoTemplate template, EventMapper mapper) { @Override public void save(User user) { List docs = - user.getUncommittedEvents().stream() - .map(mapper::toDocument) - .collect(Collectors.toList()); + user.getUncommittedEvents().stream().map(mapper::toDocument).toList(); template.insertAll(docs); }