System Architecture Overview
This movie ticketing system employs a microservices architecture with the following components:
Core Services:
- Movie Service: Handles movie data operations including creation, retrieval, and search
- User Service: Manages ticket purchasing and movie queries
- Eureka Server: Service registry for service discovery
- Config Server: Centralized configuration management from Git repository
- Zuul Gateway: API gateway with request filtering and routing
Additional Features:
- Feign client for service communication with load balancign
- Hystrix for circuit breaking and fault tolerance
- RabbitMQ for configuration refresh notifications
Project Structure
The multi-module Maven project consists of:
- common: Shared entities and utilities
- config-server: Configuration management service
- eureka-movie: Movie data provider service
- eureka-user: Ticket purchasing service
- eureka-server: Service registry
- zuul-gateway: API gateway
Core Implementation
Shared Entity Definition
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Film {
private Integer filmId;
private String title;
private Integer ticketPrice;
private String screeningStart;
private String screeningEnd;
}
Configuration Server Setup
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
Configuration server properties:
server:
port: 40010
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/your-repo/spring-cloud-config.git
Movie Service Implementation
Data access layer:
@Mapper
public interface FilmRepository {
@Insert("INSERT INTO films(title, ticket_price, screening_start, screening_end) VALUES (#{title}, #{ticketPrice}, #{screeningStart}, #{screeningEnd})")
void saveFilm(Film film);
@Select("SELECT * FROM films")
List<Film> findAllFilms();
@Select("SELECT * FROM films WHERE title = #{title}")
List<Film> findFilmsByTitle(@Param("title") String title);
}
Service controller:
@RestController
@RequestMapping("/films")
public class FilmController {
@Autowired
FilmRepository filmRepository;
@PostMapping
public void addFilm(@RequestBody Film film) {
filmRepository.saveFilm(film);
}
@GetMapping
public List<Film> getAllFilms() {
return filmRepository.findAllFilms();
}
@GetMapping("/search/{title}")
public List<Film> searchFilms(@PathVariable String title) {
return filmRepository.findFilmsByTitle(title);
}
}
User Service Implementation
Service client with Feign:
@FeignClient(value = "MOVIE-SERVICE", fallbackFactory = FilmServiceFallback.class)
public interface FilmServiceClient {
@GetMapping("/films")
List<Film> getAllFilms();
@GetMapping("/films/search/{title}")
List<Film> getFilmsByTitle(@PathVariable String title);
}
Circuit breaker fallback implementation:
@Component
public class FilmServiceFallback implements FallbackFactory<FilmServiceClient> {
@Override
public FilmServiceClient create(Throwable cause) {
return new FilmServiceClient() {
@Override
public List<Film> getAllFilms() {
cause.printStackTrace();
return Arrays.asList(new Film(503, "Service Unavailable", 0, "N/A", "N/A"));
}
@Override
public List<Film> getFilmsByTitle(String title) {
return Arrays.asList(new Film(503, "Service Unavailable", 0, "N/A", "N/A"));
}
};
}
}
Ticket purchasing controller:
@RestController
@RequestMapping("/tickets")
public class TicketController {
@Autowired
private FilmServiceClient filmServiceClient;
@Autowired
private TicketRepository ticketRepository;
@GetMapping
public List<Film> browseFilms() {
return filmServiceClient.getAllFilms();
}
@PostMapping("/purchase/{filmTitle}")
public List<Film> purchaseTicket(@PathVariable String filmTitle) {
List<Film> availableFilms = filmServiceClient.getFilmsByTitle(filmTitle);
if (availableFilms.isEmpty()) {
return Arrays.asList(new Film(404, "Film not found", 0, "N/A", "N/A"));
}
Film selectedFilm = availableFilms.get(0);
ticketRepository.savePurchase(new Film(null, selectedFilm.getTitle(),
selectedFilm.getTicketPrice(), selectedFilm.getScreeningStart(),
selectedFilm.getScreeningEnd()));
return availableFilms;
}
}
Service Discovery Configuration
Eureka server setup:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaServer
public class DiscoveryServer {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServer.class, args);
}
}
Client service registration:
eureka:
client:
service-url:
defaultZone: http://localhost:10077/eureka
instance:
prefer-ip-address: true
API Gateway Configuration
Zuul gateway setup:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
Gateway routing configuration:
zuul:
routes:
user.serviceId: user-service
user.path: /tickets/**
film.serviceId: movie-service
film.path: /films/**
ignored-services: "*"
prefix: "/api"
Load Balancing Configuration
@Configuration
public class LoadBalancerConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public IRule loadBalancingRule() {
return new RandomRule();
}
}
Service Configuration Management
Client services bootstrap configuration:
spring:
cloud:
config:
uri: http://localhost:40010
label: master
name: application-service
profile: dev
Service-specific configurations stored in Git:
User Service Configuration:
server:
port: 10088
spring:
application:
name: user-service
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
Movie Service Configuration:
server:
port: 10099
spring:
application:
name: movie-service