Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * By designing the Customer as an Aggregate Root and including a reference all of the
- * movies that it rented, we can place the validation logic for renting movies
- * directly on the customer entity.
- *
- * Advantages: More declarative-reading code. The operation is a lot closer to the
- * entity itself, which improves the discoverability and understanding what a customer
- * can do.
- *
- * Disadvantages: Additional overhead. Having to pull the ids of rented movie everytime we
- * retrieve a customer might become cumbersome + slow down performance depending on the use cases.
- */
- interface ICustomerProps {
- name: string;
- rentedMovies: MovieId[]; // one way reference to movie ids
- }
- class Customer extends AggregateRoot<ICustomerProps> {
- private constructor (props: ICustomerProps, id?: UniqueEntityId) {
- super(props, id)
- }
- public static create (props: ICustomerProps, id?: UniqueEntityId): Result<Customer> {
- // make all invariants are satisfied
- }
- public rentMovie (movie: Movie): Result<Customer> {
- // if this.rentedMovies is larger than the max amount of movies, then
- // this operation should fail
- }
- }
- /*
- * Another design: using Domain Services / Use Cases
- *
- * Advantages: Probably a better design if performance becomes a problem. Domain Services are a good
- * place to put domain logic that doesn't belong to one particular entity.
- *
- * Disadvantages:
- */
- class RentMovieUseCase {
- private rentalRepo: IrentalRepo;
- constructor (rentalRepo: IrentalRepo) {
- this.rentalRepo = rentalRepo;
- }
- public async exec (customer: Customer, movie: Movie): Result<Rental> {
- const { movieRepo } = this;
- const rentedMovies: Rental[] = await rentalRepo.getRentalsByCustomerId(customer.id);
- if (rentedMovies.length > MAX_NUM_MOVIES_TO_RENT) {
- return Result.fail<Rental>('Customer rented the max amount of movies')
- } else {
- return Rental.create({ customerId: customer.id, movieId: movie.id });
- }
- }
- }
- // Using the domain service, the controller might look more like
- class MovieController extends BaseController {
- private movieRepo: IMovieRepo;
- private customerRepo: ICustomerRepo;
- private rentalRepo: IRentalRepo;
- constructor (movieRepo: IMovieRepo, customerRepo: ICustomerRepo, rentalRepo: IRentalRepo) {
- super();
- this.movieRepo = movieRepo;
- this.customerRepo = customerRepo;
- this.rentalRepo = rentalRepo;
- }
- public async rentMovie () {
- const { req, movieRepo, customerRepo, rentalRepo } = this;
- const { movieId } = req.params['movie'];
- const { customerId } = req.params['customer'];
- const movie: Movie = await movieRepo.findById(movieId);
- const customer: Customer = await customerRepo.findById(customerId);
- if (!!movie === false) {
- return this.fail('Movie not found')
- }
- if (!!customer === false) {
- return this.fail('Customer not found')
- }
- const rentMovieResult: Result<Rental> = new RentMovieUseCase(this.rentalRepo).exec(customer, movie)
- if (rentMovieResult.isFailure) {
- return this.fail(rentMovieResult.error)
- } else {
- const rental = rentMovieResult.getValue();
- await rentalRepo.save(rental);
- return this.ok();
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement