Home > OS >  Microservices communication with optional parameters requests
Microservices communication with optional parameters requests

Time:12-05

I have a room service which returns detail for rooms when requesting http://localhost:8082/room/search/byRoomChar

    @GetMapping(path = "/search/byRoomChar")
    public @ResponseBody List<Room> byCapacity(@RequestParam(required = false) Integer capacity,
                                               @RequestParam(required = false) Boolean isUnderMaintenance,
                                               @RequestParam(required = false) String equipment) {
        return roomRepository.findByRoomChar(capacity, isUnderMaintenance, equipment);
    }

Now I want to request this @GetMapping from the booking service since this is the application gateway that users are going to interact with using the http://localhost:8081/booking/search/byRoomChar.

    @GetMapping(path = "/search/byRoomChar")
    public @ResponseBody List<Room> byCapacity(@RequestParam(required = false) Integer capacity,
                                               @RequestParam(required = false) Boolean isUnderMaintenance,
                                               @RequestParam(required = false) String equipment) {
        ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity("http://localhost:8082/room/search/byRoomChar?capacity="   capacity   "&isUnderMaintenance="  
            isUnderMaintenance   "&equipment="   equipment, Room[].class);
        return Arrays.asList(roomsResponse.getBody());
    }

Room entity code:

package nl.tudelft.sem.template.entities;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "Room")
public class Room {
    @EmbeddedId
    private RoomId id;

    @Column(name = "capacity")
    private int capacity;

    @Column(name = "numberOfPeople")
    private int numberOfPeople;

    @Column(name = "isUnderMaintenance", nullable = false)
    private boolean isUnderMaintenance;

    @Column(name = "equipment")
    private String equipment;

    public Room() {
    }

    public Room(long roomNumber, long buildingNumber, int capacity,
                int numberOfPeople, boolean isUnderMaintenance, String equipment) {
        RoomId id = new RoomId(roomNumber, buildingNumber);
        this.id = id;
        this.capacity = capacity;
        this.numberOfPeople = numberOfPeople;
        this.isUnderMaintenance = isUnderMaintenance;
        this.equipment = equipment;
    }

    public RoomId getId() {
        return id;
    }

    public void setId(RoomId id) {
        this.id = id;
    }

    public int getCapacity() {
        return capacity;
    }

    public void setCapacity(int capacity) {
        this.capacity = capacity;
    }

    public int getNumberOfPeople() {
        return numberOfPeople;
    }

    public void setNumberOfPeople(int numberOfPeople) {
        this.numberOfPeople = numberOfPeople;
    }

    public boolean getIsUnderMaintenance() {
        return isUnderMaintenance;
    }

    public void setUnderMaintenance(boolean underMaintenance) {
        isUnderMaintenance = underMaintenance;
    }

    public String getEquipment() {
        return equipment;
    }

    public void setEquipment(String equipment) {
        this.equipment = equipment;
    }
}

Room repository code:

package nl.tudelft.sem.template.repositories;

import java.util.List;
import nl.tudelft.sem.template.entities.Room;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface RoomRepository extends JpaRepository<Room, Integer> {
    @Query("SELECT r FROM Room r WHERE (:number is null or r.id.number = :number)"
          "and r.id.buildingNumber = :buildingNumber")
    List<Room> findByRoomNum(@Param("number") Long number,
                             @Param("buildingNumber") Long buildingNumber);

    @Query("SELECT r FROM Room r WHERE (:capacity is null or r.capacity = :capacity) and"
          "(:isUnderMaintenance is null or r.isUnderMaintenance = :isUnderMaintenance) and"
          "(:equipment is null or r.equipment = :equipment)")
    List<Room> findByRoomChar(@Param("capacity") Integer capacity,
                              @Param("isUnderMaintenance") Boolean isUnderMaintenance,
                              @Param("equipment") String equipment);

}

However, this does not work because when omitting parameters when calling the getmapping from the booking service all parameter values are turned into null because of the required=false. And this are converted into Strings inside the hard coded url.

2021-12-04 17:13:03.883  WARN 16920 --- [nio-8082-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Boolean'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [null]]

How can I make a get http request with optional parameters from within the code?

CodePudding user response:

This is because the URI string being built when the parameters are null is like the follwing:

http://localhost:8082/room/search/byRoomChar?isUnderMaintenance=null

Since the "null" is being appended as a value of a parameter, the room server fails trying to deserialize it to a different type. For example in the error message you gave means that the "isUnderMaintenance" should be boolean but is "null" string.

To solve this problem, I recommend using the UriComponentBuilder.

@Test
fun constructUriWithQueryParameter() {
    val uri = UriComponentsBuilder.newInstance()
        .scheme("http")
        .host("localhost")
        .port(8082)
        .path("/room/search/byRoomChar")
        .query("capacity={capa}")
        .query("isUnderMaintenance={isUnderMaintenance}")
        .query("equipment={equip}")
        .buildAndExpand(null, null, null)
        .toUriString()
    assertEquals(
        "http://localhost:8082/room/search/byRoomChar 
             capacity=&isUnderMaintenance=&equipment=",
        uri
    )
}

CodePudding user response:

UriComponentsBuilder can help with URI construction. It correctly handles nullable query parameters.

String uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8082/room/search/byRoomChar")
            .queryParam("capacity", capacity)
            .queryParam("isUnderMaintenance", isUnderMaintenance)
            .queryParam("equipment", equipment)
            .encode().toUriString();
ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity(uri, Room[].class);

Also, following answer can be helpful: https://stackoverflow.com/a/25434451/5990117

CodePudding user response:

If the parameters are not mandatory in your Room API but you still use them in your call to the Database either you have sensible defaults if they are actually not provided by the user. Something along the following lines (in this case you actually don't need to explicitly define required = false):

@GetMapping(path = "/search/byRoomChar")
public @ResponseBody List<Room> byCapacity(@RequestParam(defaultValue = "10") Integer capacity,
                                           @RequestParam(defaultValue = "false") Boolean isUnderMaintenance,
                                           @RequestParam(defaultValue = "default-equipment") String equipment) {
    return roomRepository.findByRoomChar(capacity, isUnderMaintenance, equipment);
}

Or you define a Repository method with no additional parameters, but this might be trickier since you basically need all the possibilities of null and non-null parameters.

  • Related