From a1dfdfef3dc385fcb38912162d5487fa2073b5a9 Mon Sep 17 00:00:00 2001 From: Luis Machado Reis Date: Wed, 30 Apr 2025 22:01:23 -0300 Subject: [PATCH 1/3] Adding Clock support to Mapper, Configs, and Tests --- README.md | 250 +++++++++--------- .../common/infrastructure/ClockConfig.java | 27 ++ .../infrastructure/ClockProperties.java | 21 ++ .../ping/application/dto/PingMapper.java | 9 +- src/main/resources/application.yml | 4 +- .../pubsub/FlightDataSubscriberTest.java | 8 +- .../ping/application/dto/PingMapperTest.java | 14 +- 7 files changed, 203 insertions(+), 130 deletions(-) create mode 100644 src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java create mode 100644 src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java diff --git a/README.md b/README.md index 19f6e24..a517f64 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,122 @@ A Spring Boot application for tracking flight events. ![Coverage](badges/jacoco.svg) ![Branches](badges/branches.svg) -## Getting Started +## Features + +- Real-time flight position tracking +- Kafka-based event streaming +- PostgreSQL database with read-write routing +- Redis caching +- Swagger/OpenAPI documentation +- Configurable timezone support -### Prerequisites +## Prerequisites -- Java 21 -- Maven 3.9.6 +- Java 17 or later - Docker and Docker Compose +- PostgreSQL +- Redis +- Kafka + +## Configuration + +The application can be configured through `application.yml`. Key configurations include: + +### Database + +```yaml +spring: + datasource: + writer: + jdbcUrl: jdbc:postgresql://localhost:5432/flighttracker + username: flighttracker + password: flighttracker + reader: + jdbcUrl: jdbc:postgresql://localhost:5433/flighttracker + username: flighttracker + password: flighttracker +``` + +### Kafka + +```yaml +spring: + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: flight-tracker-group + topic: + flight-positions: flight-positions + ping-created: ping-created +``` + +### Redis + +```yaml +spring: + redis: + host: localhost + port: 6379 +``` + +### Clock Configuration + +The application uses a configurable clock for timestamp operations. By default, it uses UTC: + +```yaml +app: + clock: + timezone: UTC +``` + +You can change the timezone to any valid timezone ID (e.g., "America/New_York", "Europe/London"): + +```yaml +app: + clock: + timezone: America/New_York +``` + +### API Documentation + +Swagger UI is available at `/swagger-ui.html` with the following configuration: + +```yaml +springdoc: + api-docs: + path: /api-docs + swagger-ui: + path: /swagger-ui.html +``` + +## Development + +### Running the Application + +1. Start the required services using Docker Compose: + ```bash + docker-compose up -d + ``` + +2. Run the application: + ```bash + ./mvnw spring-boot:run + ``` + +### Testing + +Run the tests: +```bash +./mvnw test +``` + +## API Documentation + +The API documentation is available at: +- Swagger UI: http://localhost:8080/swagger-ui.html +- OpenAPI JSON: http://localhost:8080/api-docs + +## Getting Started ### External Dependencies @@ -120,12 +229,6 @@ cd flight-tracker-event-server-java mvn clean install ``` -### Running - -```bash -mvn spring-boot:run -``` - ## Contributing 1. Fork the repository @@ -193,117 +296,22 @@ app: ## Project Structure ``` -. -├── .github/ -│ └── workflows/ -│ └── maven.yml -├── src/ -│ ├── main/ -│ │ ├── java/ -│ │ │ └── dev/ -│ │ │ └── luismachadoreis/ -│ │ │ └── flighttracker/ -│ │ │ └── server/ -│ │ │ ├── common/ -│ │ │ │ ├── application/ -│ │ │ │ │ └── cqs/ -│ │ │ │ │ ├── command/ -│ │ │ │ │ │ ├── Command.java -│ │ │ │ │ │ └── CommandHandler.java -│ │ │ │ │ ├── query/ -│ │ │ │ │ │ ├── Query.java -│ │ │ │ │ │ └── QueryHandler.java -│ │ │ │ │ └── mediator/ -│ │ │ │ │ ├── Mediator.java -│ │ │ │ │ └── SpringMediator.java -│ │ │ │ └── infrastructure/ -│ │ │ │ ├── datasource/ -│ │ │ │ │ ├── DbContextHolder.java -│ │ │ │ │ ├── ReadWriteRoutingAspect.java -│ │ │ │ │ ├── ReadWriteRoutingProperties.java -│ │ │ │ │ └── RoutingDataSource.java -│ │ │ │ ├── DatasourceConfig.java -│ │ │ │ ├── KafkaConfig.java -│ │ │ │ └── OpenApiConfig.java -│ │ │ ├── flightdata/ -│ │ │ │ └── infrastructure/ -│ │ │ │ ├── kafka/ -│ │ │ │ │ └── FlightDataConsumerConfig.java -│ │ │ │ └── pubsub/ -│ │ │ │ └── FlightDataSubscriber.java -│ │ │ ├── ping/ -│ │ │ │ ├── api/ -│ │ │ │ │ └── PingController.java -│ │ │ │ ├── application/ -│ │ │ │ │ ├── dto/ -│ │ │ │ │ │ ├── FlightDataDTO.java -│ │ │ │ │ │ ├── PingDTO.java -│ │ │ │ │ │ └── PingDTOMapper.java -│ │ │ │ │ └── usecase/ -│ │ │ │ │ ├── CreatePingCommand.java -│ │ │ │ │ ├── CreatePingCommandHandler.java -│ │ │ │ │ ├── GetRecentPingsQuery.java -│ │ │ │ │ └── GetRecentPingsQueryHandler.java -│ │ │ │ ├── domain/ -│ │ │ │ │ ├── event/ -│ │ │ │ │ │ └── PingCreated.java -│ │ │ │ │ ├── Ping.java -│ │ │ │ │ └── PingRepository.java -│ │ │ │ └── infrastructure/ -│ │ │ │ ├── pubsub/ -│ │ │ │ │ └── ping/ -│ │ │ │ │ └── PingEventPublisher.java -│ │ │ │ └── repository/ -│ │ │ │ └── JpaPingRepository.java -│ │ │ └── FlightTrackerApplication.java -│ │ └── resources/ -│ │ ├── application.yml -│ │ └── application-test.yml -│ └── test/ -│ └── java/ -│ └── dev/ -│ └── luismachadoreis/ -│ └── flighttracker/ -│ └── server/ -│ ├── common/ -│ │ ├── application/ -│ │ │ └── cqs/ -│ │ │ └── mediator/ -│ │ │ └── SpringMediatorTest.java -│ │ └── infrastructure/ -│ │ └── datasource/ -│ │ ├── ReadWriteRoutingAspectTest.java -│ │ └── RoutingDataSourceTest.java -│ ├── flightdata/ -│ │ └── infrastructure/ -│ │ ├── kafka/ -│ │ │ └── FlightDataConsumerConfigTest.java -│ │ └── pubsub/ -│ │ └── FlightDataSubscriberTest.java -│ ├── ping/ -│ │ ├── api/ -│ │ │ └── PingControllerTest.java -│ │ ├── application/ -│ │ │ ├── dto/ -│ │ │ │ ├── FlightDataDTOTest.java -│ │ │ │ ├── PingDTOTest.java -│ │ │ │ └── PingDTOMapperTest.java -│ │ │ └── usecase/ -│ │ │ ├── CreatePingCommandHandlerTest.java -│ │ │ └── GetRecentPingsQueryHandlerTest.java -│ │ └── domain/ -│ │ └── PingTest.java -│ └── SpringContextTest.java -├── badges/ -│ ├── jacoco.svg -│ └── branches.svg -├── db/ -│ └── init-scripts/ -│ └── 01-init.sql -├── docker-compose.yml -├── LICENSE.md -├── pom.xml -└── README.md +src/ +├── main/ +│ ├── java/ +│ │ └── dev/luismachadoreis/flighttracker/server/ +│ │ ├── common/ # Common infrastructure and utilities +│ │ ├── flightdata/ # Flight data processing +│ │ └── ping/ # Ping domain and API +│ └── resources/ +│ ├── application.yml # Main configuration +│ └── application-test.yml # Test configuration +└── test/ + └── java/ + └── dev/luismachadoreis/flighttracker/server/ + ├── common/ # Common infrastructure tests + ├── flightdata/ # Flight data tests + └── ping/ # Ping domain and API tests ``` ## License diff --git a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java new file mode 100644 index 0000000..e64c732 --- /dev/null +++ b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java @@ -0,0 +1,27 @@ +package dev.luismachadoreis.flighttracker.server.common.infrastructure; + +import dev.luismachadoreis.flighttracker.server.common.infrastructure.config.ClockProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.Clock; +import java.time.ZoneId; + +/** + * Configuration for Clock beans. + */ +@Configuration +@EnableConfigurationProperties(ClockProperties.class) +public class ClockConfig { + + /** + * Creates a new Clock bean using the configured timezone. + * @param properties The clock properties + * @return the Clock + */ + @Bean + public Clock clock(ClockProperties properties) { + return Clock.system(ZoneId.of(properties.timezone())); + } +} \ No newline at end of file diff --git a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java new file mode 100644 index 0000000..a5a9ffd --- /dev/null +++ b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java @@ -0,0 +1,21 @@ +package dev.luismachadoreis.flighttracker.server.common.infrastructure.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for clock settings. + */ +@ConfigurationProperties(prefix = "app.clock") +public record ClockProperties( + /** + * The timezone to use for the clock. + * Defaults to "UTC" if not specified. + */ + String timezone +) { + public ClockProperties { + if (timezone == null) { + timezone = "UTC"; + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapper.java b/src/main/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapper.java index 0f33b78..b3515f5 100644 --- a/src/main/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapper.java +++ b/src/main/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapper.java @@ -2,12 +2,19 @@ import dev.luismachadoreis.flighttracker.server.ping.domain.Ping; import org.springframework.stereotype.Component; +import java.time.Clock; import java.time.Instant; import java.util.UUID; @Component public class PingMapper { + private final Clock clock; + + public PingMapper(Clock clock) { + this.clock = clock; + } + /** * Map a Ping to a PingDTO. * @param ping The Ping to map. @@ -107,7 +114,7 @@ public PingDTO fromFlightData(FlightDataDTO flightData) { flightData.positionSource(), flightData.timePosition() != null ? Instant.ofEpochSecond(flightData.timePosition()) : null ), - Instant.now() + Instant.now(clock) ); } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 14b8d2b..04a6395 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -91,4 +91,6 @@ api: app: read-write-routing: - enabled: false \ No newline at end of file + enabled: false + clock: + timezone: UTC \ No newline at end of file diff --git a/src/test/java/dev/luismachadoreis/flighttracker/server/flightdata/infrastructure/pubsub/FlightDataSubscriberTest.java b/src/test/java/dev/luismachadoreis/flighttracker/server/flightdata/infrastructure/pubsub/FlightDataSubscriberTest.java index a605c3c..3cb8581 100644 --- a/src/test/java/dev/luismachadoreis/flighttracker/server/flightdata/infrastructure/pubsub/FlightDataSubscriberTest.java +++ b/src/test/java/dev/luismachadoreis/flighttracker/server/flightdata/infrastructure/pubsub/FlightDataSubscriberTest.java @@ -12,7 +12,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.Clock; import java.time.Instant; +import java.time.ZoneId; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -29,16 +31,18 @@ class FlightDataSubscriberTest { private PingMapper pingMapper; private FlightDataSubscriber subscriber; + private Clock fixedClock; @BeforeEach void setUp() { + fixedClock = Clock.fixed(Instant.parse("2024-01-01T00:00:00Z"), ZoneId.of("UTC")); subscriber = new FlightDataSubscriber(mediator, pingMapper); } @Test void shouldConsumeFlightDataAndCreatePing() { // Given - Long now = Instant.now().getEpochSecond(); + Long now = Instant.now(fixedClock).getEpochSecond(); FlightDataDTO flightData = new FlightDataDTO( "ABC123", "FL123", @@ -84,7 +88,7 @@ void shouldConsumeFlightDataAndCreatePing() { flightData.positionSource(), Instant.ofEpochSecond(flightData.timePosition()) ), - Instant.now() + Instant.now(fixedClock) ); when(pingMapper.fromFlightData(flightData)).thenReturn(expectedPingDTO); diff --git a/src/test/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapperTest.java b/src/test/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapperTest.java index e560815..b79a636 100644 --- a/src/test/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapperTest.java +++ b/src/test/java/dev/luismachadoreis/flighttracker/server/ping/application/dto/PingMapperTest.java @@ -4,7 +4,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.time.Clock; import java.time.Instant; +import java.time.ZoneId; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -12,16 +14,18 @@ class PingMapperTest { private PingMapper mapper; + private Clock fixedClock; @BeforeEach void setUp() { - mapper = new PingMapper(); + fixedClock = Clock.fixed(Instant.parse("2024-01-01T00:00:00Z"), ZoneId.of("UTC")); + mapper = new PingMapper(fixedClock); } @Test void shouldMapFlightDataToPingDTO() { // Given - Long now = Instant.now().getEpochSecond(); + Long now = Instant.now(fixedClock).getEpochSecond(); var flightData = new FlightDataDTO( "ABC123", "FL123", @@ -47,7 +51,7 @@ void shouldMapFlightDataToPingDTO() { // Then assertThat(pingDTO.id()).isNotNull(); - assertThat(pingDTO.lastUpdate()).isNotNull(); + assertThat(pingDTO.lastUpdate()).isEqualTo(Instant.now(fixedClock)); // Aircraft data assertThat(pingDTO.aircraft().icao24()).isEqualTo(flightData.icao24()); @@ -76,7 +80,7 @@ void shouldMapFlightDataToPingDTO() { @Test void shouldMapPingToDTO() { // Given - var now = Instant.now(); + var now = Instant.now(fixedClock); var aircraft = new Ping.Aircraft("ABC123", "FL123", "US", now, "7700", true, new Integer[]{1, 2}); var vector = new Ping.Vector(500.0, 180.0, 0.0); var position = new Ping.Position(10.0, 20.0, 30000.0, 29000.0, false, 1, now); @@ -97,7 +101,7 @@ void shouldMapPingToDTO() { @Test void shouldMapDTOToDomain() { // Given - var now = Instant.now(); + var now = Instant.now(fixedClock); var dto = new PingDTO( null, new PingDTO.Aircraft("ABC123", "FL123", "US", now, "7700", true, new Integer[]{1, 2}), From b4e9e4b495afce9b33bc30b156033b394f01563d Mon Sep 17 00:00:00 2001 From: Luis Machado Reis Date: Wed, 30 Apr 2025 22:05:40 -0300 Subject: [PATCH 2/3] Update src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../server/common/infrastructure/ClockProperties.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java index a5a9ffd..b0e1100 100644 --- a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java +++ b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java @@ -14,8 +14,6 @@ public record ClockProperties( String timezone ) { public ClockProperties { - if (timezone == null) { - timezone = "UTC"; - } + timezone = Objects.requireNonNullElse(timezone, "UTC"); } } \ No newline at end of file From 15ef4efa0d23c1f974400e311672228e8853cd8e Mon Sep 17 00:00:00 2001 From: Luis Machado Reis Date: Wed, 30 Apr 2025 22:14:53 -0300 Subject: [PATCH 3/3] Fixing GitHub Copilot Bug --- .../server/common/infrastructure/ClockConfig.java | 1 - .../server/common/infrastructure/ClockProperties.java | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java index e64c732..bdec6fd 100644 --- a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java +++ b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockConfig.java @@ -1,6 +1,5 @@ package dev.luismachadoreis.flighttracker.server.common.infrastructure; -import dev.luismachadoreis.flighttracker.server.common.infrastructure.config.ClockProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java index b0e1100..1126fe9 100644 --- a/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java +++ b/src/main/java/dev/luismachadoreis/flighttracker/server/common/infrastructure/ClockProperties.java @@ -1,4 +1,6 @@ -package dev.luismachadoreis.flighttracker.server.common.infrastructure.config; +package dev.luismachadoreis.flighttracker.server.common.infrastructure; + +import java.util.Objects; import org.springframework.boot.context.properties.ConfigurationProperties;