这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions spring-boot-modules/spring-boot-libraries-3/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@
<version>${spring-modulith.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-core</artifactId>
<version>${spring-modulith.version}</version>
</dependency>

<dependency>
<groupId>org.jmolecules</groupId>
<artifactId>jmolecules-cqrs-architecture</artifactId>
<version>${jmolecules-cqrs-architecture.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -123,19 +134,36 @@
<artifactId>jolokia-support-spring</artifactId>
<version>${jolokia-support-spring.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback-core.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback-core.version}</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>8.1</version>
</dependency>
</dependencies>


<properties>
<spring-boot.version>3.1.5</spring-boot.version>
<spring-modulith.version>1.1.3</spring-modulith.version>
<spring-boot.version>3.5.4</spring-boot.version>
<spring-modulith.version>1.4.2</spring-modulith.version>
<testcontainers.version>1.19.3</testcontainers.version>
<awaitility.version>4.2.0</awaitility.version>
<postgresql.version>42.3.1</postgresql.version>
<h2.version>2.2.224</h2.version>
<jolokia-support-spring.version>2.2.1</jolokia-support-spring.version>
<eventuate.tram.version>0.36.0.RELEASE</eventuate.tram.version>
<json-unit-assertj.version>3.5.0</json-unit-assertj.version>
<jmolecules-cqrs-architecture.version>1.10.0</jmolecules-cqrs-architecture.version>
<logback-core.version>1.5.18</logback-core.version>
</properties>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* use the appropriate profile: eventuate|modulith
* use the appropriate profile: eventuate|modulith|cqrs
*/
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run( Application.class, args);
SpringApplication.run(Application.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.baeldung.spring.modulith.cqrs.movie;

import java.time.Instant;
import java.util.List;

import org.jmolecules.architecture.cqrs.QueryModel;

@QueryModel
record AvailableMovieSeats(String title, String screenRoom, Instant startTime, List<String> freeSeats) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.baeldung.spring.modulith.cqrs.movie;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;

@Entity
class Movie {

@Id
@GeneratedValue
private Long id;
private String title;
private String screenRoom;
private Instant startTime;

@ElementCollection
@CollectionTable(name = "screen_room_free_seats", joinColumns = @JoinColumn(name = "room_id"))
@Column(name = "seat_number")
private List<String> freeSeats = allSeats();

@ElementCollection
@CollectionTable(name = "screen_room_occupied_seats", joinColumns = @JoinColumn(name = "room_id"))
@Column(name = "seat_number")
private List<String> occupiedSeats = new ArrayList<>();

Movie(String movieName, String screenRoom, Instant startTime) {
this.title = movieName;
this.screenRoom = screenRoom;
this.startTime = startTime;
}

void occupySeat(String seatNumber) {
if (freeSeats.contains(seatNumber)) {
freeSeats.remove(seatNumber);
occupiedSeats.add(seatNumber);
} else {
throw new IllegalArgumentException("Seat " + seatNumber + " is not available.");
}
}

void freeSeat(String seatNumber) {
if (occupiedSeats.contains(seatNumber)) {
occupiedSeats.remove(seatNumber);
freeSeats.add(seatNumber);
} else {
throw new IllegalArgumentException("Seat " + seatNumber + " is not currently occupied.");
}
}

static List<String> allSeats() {
List<Integer> rows = IntStream.range(1, 20)
.boxed()
.toList();

return IntStream.rangeClosed('A', 'J')
.mapToObj(c -> String.valueOf((char) c))
.flatMap(col -> rows.stream()
.map(row -> col + row))
.sorted()
.toList();
}

protected Movie() {
// Default constructor for JPA
}

Instant startTime() {
return startTime;
}

String title() {
return title;
}

String screenRoom() {
return screenRoom;
}

List<String> freeSeats() {
return List.copyOf(freeSeats);
}

List<String> occupiedSeatsSeats() {
return List.copyOf(freeSeats);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.baeldung.spring.modulith.cqrs.movie;

import static java.time.Instant.now;
import static java.time.temporal.ChronoUnit.DAYS;

import java.time.Instant;
import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/movies")
class MovieController {

private final MovieRepository movieScreens;

MovieController(MovieRepository screenRooms) {
this.movieScreens = screenRooms;
}

/*
curl -X GET "http://localhost:8080/api/seating/movies?range=week"
*/
@GetMapping
List<UpcomingMovies> moviesToday(@RequestParam String range) {
Instant endTime = endTime(range);
return movieScreens.findUpcomingMoviesByStartTimeBetween(now(), endTime.truncatedTo(DAYS));
}

/*
curl -X GET http://localhost:8080/api/movies/1/seats
*/
@GetMapping("/{movieId}/seats")
ResponseEntity<AvailableMovieSeats> movieSeating(@PathVariable Long movieId) {
return ResponseEntity.of(movieScreens.findAvailableSeatsByMovieId(movieId));
}

private static Instant endTime(String range) {
return switch (range) {
case "day" -> now().plus(1, DAYS);
case "week" -> now().plus(7, DAYS);
case "month" -> now().plus(30, DAYS);
default -> throw new IllegalArgumentException("Invalid range: " + range);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.baeldung.spring.modulith.cqrs.movie;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

interface MovieRepository extends JpaRepository<Movie, Long> {

List<UpcomingMovies> findUpcomingMoviesByStartTimeBetween(Instant start, Instant end);

default Optional<AvailableMovieSeats> findAvailableSeatsByMovieId(Long movieId) {
return findById(movieId).map(movie -> new AvailableMovieSeats(movie.title(), movie.screenRoom(), movie.startTime(), movie.freeSeats()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.baeldung.spring.modulith.cqrs.movie;

import static java.time.temporal.ChronoUnit.HOURS;

import java.time.Instant;
import java.util.List;
import java.util.stream.LongStream;

import org.jmolecules.event.annotation.DomainEventHandler;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.modulith.events.ApplicationModuleListener;
import org.springframework.stereotype.Component;

import com.baeldung.spring.modulith.cqrs.ticket.BookingCancelled;
import com.baeldung.spring.modulith.cqrs.ticket.BookingCreated;

@Component
class TicketBookingEventHandler {

private final MovieRepository screenRooms;

TicketBookingEventHandler(MovieRepository screenRooms) {
this.screenRooms = screenRooms;
}

@DomainEventHandler
@ApplicationModuleListener
void handleTicketBooked(BookingCreated booking) {
Movie room = screenRooms.findById(booking.movieId())
.orElseThrow();

room.occupySeat(booking.seatNumber());
screenRooms.save(room);
}

@DomainEventHandler
@ApplicationModuleListener
void handleTicketCancelled(BookingCancelled cancellation) {
Movie room = screenRooms.findById(cancellation.movieId())
.orElseThrow();

room.freeSeat(cancellation.seatNumber());
screenRooms.save(room);
}

@EventListener(ApplicationReadyEvent.class)
void insertDummyMovies() {
List<Movie> dummyMovies = LongStream.range(1, 30)
.mapToObj(nr -> new Movie("Dummy movie #" + nr, "Screen #" + nr % 5, Instant.now()
.plus(nr, HOURS)))
.toList();
screenRooms.saveAll(dummyMovies);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.baeldung.spring.modulith.cqrs.movie;

import java.time.Instant;

import org.jmolecules.architecture.cqrs.QueryModel;

@QueryModel
record UpcomingMovies(Long id, String title, Instant startTime) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.baeldung.spring.modulith.cqrs.ticket;

import org.jmolecules.architecture.cqrs.Command;

@Command
record BookTicket(Long movieId, String seat) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.baeldung.spring.modulith.cqrs.ticket;

import java.time.Instant;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
class BookedTicket {

@Id
@GeneratedValue
private Long id;
private Long movieId;
private String seatNumber;
private Instant createdAt = Instant.now();
private Status status = Status.BOOKED;

BookedTicket cancelledBooking() {
BookedTicket cancelled = new BookedTicket(movieId, seatNumber);
cancelled.status = Status.BOOKING_CANCELLED;
return cancelled;
}

enum Status {
BOOKED,
BOOKING_CANCELLED
}

boolean isBooked() {
return status == Status.BOOKED;
}

boolean isCancelled() {
return status == Status.BOOKING_CANCELLED;
}

BookedTicket(Long movieId, String seatNumber) {
this.movieId = movieId;
this.seatNumber = seatNumber;
}

Long id() {
return id;
}

Long movieId() {
return movieId;
}

String seatNumber() {
return seatNumber;
}

Instant createdAt() {
return createdAt;
}

Status status() {
return status;
}

protected BookedTicket() {
// Default constructor for JPA
}
}
Loading