Room Product API: Get Available Items Easily

by Admin 45 views
Room Product API: Get Available Items Easily

Have you ever wondered how to make the user experience smoother when booking rooms? A new API is here to help! This article dives into the details of the newly proposed API that allows users to easily view all available products for a specific room before making a reservation. We'll explore the business needs, technical implementation, and overall impact of this exciting addition.

The Need for a Room-Specific Product API

Currently, there isn't a straightforward way for regular users to see which products they can add to their room booking. The existing RoomAllowedProductController is designed for admin use, and the standard ProductController endpoints don't filter products based on room-specific allowances. This can lead to a clunky user experience where customers only find out about available add-ons during the booking process. The new API aims to fix this by providing a simple and efficient way to list all products applicable to a particular room.

Business Requirements Driving the API

The primary goal is to enhance the user experience by allowing users to see available product options upfront. This empowers them to make informed decisions and potentially increase their spending. Specifically, the business requirements are:

  • Easy Access to Product List: Users should be able to quickly see a list of products they can add to their room booking.
  • No Inventory Information Needed: The API should focus on listing available products, not providing real-time inventory counts. This simplifies the initial implementation.
  • Room-Specific Product Filtering: The API must only return products that are actually available for the selected room.

What Products Should Be Included?

The API needs to consider different scopes when determining which products are available for a room. Here's a breakdown of the product types that should be included in the API response:

  1. ROOM Scope Products: These are products specifically associated with the requested roomId. For example, a "Deluxe Room Package" that is only available for a specific room.
  2. PLACE Scope Products: These products are allowed for the room based on entries in the RoomAllowedProduct table. This allows administrators to define specific products that are available in certain rooms within a place.
  3. RESERVATION Scope Products: These are products that are available for all rooms, regardless of the roomId. Think of these as general add-ons like "Early Check-in" or "Late Check-out."

Proposed API Endpoint

To fulfill these requirements, two potential API endpoints are proposed:

  • GET /api/v1/rooms/{roomId}/available-products?placeId={placeId}
  • GET /api/v1/products/available-for-room?roomId={roomId}&placeId={placeId}

Both endpoints achieve the same goal, but the choice depends on how you want to structure your API. The first option groups the products under the /rooms resource, while the second groups them under the /products resource. Either approach is valid, so choose the one that best fits your overall API design.

Impact on the System

Implementing this API will have implications across different parts of the system. Let's break down the impact on the backend, database, and testing.

Backend

The backend will require the following changes:

  • UseCase: A new GetAvailableProductsForRoomUseCase will be created to encapsulate the logic for retrieving available products.
  • Service: A service layer (either a domain service or an application service) will be implemented to orchestrate the retrieval of products from different sources.
  • Repository: The repository layer will need to support complex queries, potentially involving JOIN operations or a combination of multiple queries to retrieve products based on the different scopes.
  • Controller: A new endpoint will be added to either the ProductController or a separate RoomProductController to handle the API request.
  • DTO: An AvailableProductResponse DTO will be created to structure the API response.

Database

The existing database tables (products and room_allowed_products) will be used without any modifications. The API will primarily involve read operations to retrieve data from these tables.

Testing

Thorough testing is crucial to ensure the API functions correctly. The following types of tests will be implemented:

  • Unit Tests: Unit tests will be written for the UseCase and service layers to verify the logic for retrieving and filtering products.
  • Integration Tests: Integration tests will be created to test the interaction between the controller, service, and repository layers.
  • Scenario Tests: Scenario tests will be designed to verify that all three scopes of products (ROOM, PLACE, and RESERVATION) are correctly retrieved and included in the API response.

Decision, Alternatives, and Rationale

Why is this API needed?

As mentioned earlier, the current system lacks a dedicated API for retrieving available products for a specific room. The existing RoomAllowedProductController is designed for administrative purposes and doesn't cater to the needs of regular users. The new API fills this gap by providing a user-friendly way to discover available add-ons.

Implementation Complexity

The estimated implementation complexity is medium, requiring approximately 1-2 days of development effort. The existing domain models can be reused, and the business logic is relatively straightforward, primarily involving data retrieval and filtering.

Deep Dive into Backend Implementation

Okay, let's get a bit more technical and dive into how this API might be implemented on the backend. We'll explore the UseCase, Service, Repository, Controller, and DTO in more detail.

UseCase: GetAvailableProductsForRoomUseCase

The UseCase is the entry point for the API's business logic. It receives the roomId and placeId as input and orchestrates the retrieval of available products. Here's a simplified example:

public class GetAvailableProductsForRoomUseCase {

    private final ProductService productService;

    public GetAvailableProductsForRoomUseCase(ProductService productService) {
        this.productService = productService;
    }

    public List<AvailableProductResponse> execute(Long roomId, Long placeId) {
        return productService.getAvailableProductsForRoom(roomId, placeId);
    }
}

This UseCase simply delegates the actual product retrieval to a ProductService. This keeps the UseCase focused on its core responsibility: orchestrating the business logic.

Service: ProductService

The ProductService is responsible for retrieving products from different sources and combining them into a single list. It might look something like this:

public interface ProductService {
    List<AvailableProductResponse> getAvailableProductsForRoom(Long roomId, Long placeId);
}

@Service
public class ProductServiceImpl implements ProductService {

    private final ProductRepository productRepository;
    private final RoomAllowedProductRepository roomAllowedProductRepository;

    public ProductServiceImpl(ProductRepository productRepository, RoomAllowedProductRepository roomAllowedProductRepository) {
        this.productRepository = productRepository;
        this.roomAllowedProductRepository = roomAllowedProductRepository;
    }

    @Override
    public List<AvailableProductResponse> getAvailableProductsForRoom(Long roomId, Long placeId) {
        List<Product> roomScopeProducts = productRepository.findByRoomId(roomId);
        List<Product> placeScopeProducts = roomAllowedProductRepository.findProductsByRoomIdAndPlaceId(roomId, placeId);
        List<Product> reservationScopeProducts = productRepository.findByScope(ProductScope.RESERVATION);

        // Combine and deduplicate the lists
        List<Product> allProducts = new ArrayList<>();
        allProducts.addAll(roomScopeProducts);
        allProducts.addAll(placeScopeProducts);
        allProducts.addAll(reservationScopeProducts);

        return allProducts.stream()
                .distinct()
                .map(this::convertToAvailableProductResponse)
                .collect(Collectors.toList());
    }

    private AvailableProductResponse convertToAvailableProductResponse(Product product) {
        // Map Product entity to AvailableProductResponse DTO
    }
}

This service retrieves products from the ProductRepository and RoomAllowedProductRepository based on the different scopes. It then combines these lists, removes any duplicates, and maps the Product entities to AvailableProductResponse DTOs.

Repository: ProductRepository and RoomAllowedProductRepository

The repositories are responsible for interacting with the database. The ProductRepository might have methods like:

public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByRoomId(Long roomId);
    List<Product> findByScope(ProductScope scope);
}

And the RoomAllowedProductRepository might have methods like:

public interface RoomAllowedProductRepository extends JpaRepository<RoomAllowedProduct, Long> {
    @Query("SELECT rap.product FROM RoomAllowedProduct rap WHERE rap.roomId = :roomId AND rap.placeId = :placeId")
    List<Product> findProductsByRoomIdAndPlaceId(@Param("roomId") Long roomId, @Param("placeId") Long placeId);
}

These repositories provide the methods needed to retrieve products based on different criteria.

Controller: RoomProductController

The controller handles the incoming API request and returns the response. It might look like this:

@RestController
@RequestMapping("/api/v1/rooms/{roomId}")
public class RoomProductController {

    private final GetAvailableProductsForRoomUseCase getAvailableProductsForRoomUseCase;

    public RoomProductController(GetAvailableProductsForRoomUseCase getAvailableProductsForRoomUseCase) {
        this.getAvailableProductsForRoomUseCase = getAvailableProductsForRoomUseCase;
    }

    @GetMapping("/available-products")
    public ResponseEntity<List<AvailableProductResponse>> getAvailableProducts(@PathVariable Long roomId, @RequestParam Long placeId) {
        List<AvailableProductResponse> products = getAvailableProductsForRoomUseCase.execute(roomId, placeId);
        return ResponseEntity.ok(products);
    }
}

This controller receives the roomId and placeId from the request, calls the GetAvailableProductsForRoomUseCase to retrieve the products, and returns the response as a list of AvailableProductResponse DTOs.

DTO: AvailableProductResponse

The AvailableProductResponse DTO is a simple data transfer object that represents the product information returned by the API. It might include fields like:

public class AvailableProductResponse {
    private Long id;
    private String name;
    private String description;
    private Double price;
    // Getters and setters
}

This DTO provides a clean and consistent way to represent product information in the API response.

Conclusion

This new API will significantly improve the user experience by providing a clear and concise list of available products for each room. By considering different product scopes and implementing a robust backend architecture, this API will streamline the booking process and empower users to make informed decisions. The implementation complexity is manageable, and the benefits to the user experience make this a worthwhile addition to the system. So, get ready to roll up your sleeves and start coding! Your users will thank you for it!