-1

Let's say I want to build a rest api for storing car information. To make it simple for the sake of this post let's say I would like it to look like this:

/api/cars/{carmake}/save
/api/cars/{carmake}/edit
/api/cars/{carmake}/delete

Now, let's say I have multiple car makes and each of them would require different car make services eg. BmwService, MercedesService, AudiService.

So this is my idea: one abstract controller that would look something like this:

@RequestMapping(value="/api/cars/")
public abstract class CarController {
    protected final String CAR_MAKE;

    public CarController(String carMake){
        this.CAR_MAKE = carMake;
    }

    @PostMapping(value = CAR_MAKE + "/save")
    public abstract void save(@Valid @RequestBody Serializable car)

    @DeleteMapping(value = CAR_MAKE + "/delete")
    public abstract void save(@Valid @RequestBody Serializable car);

    @PatchMapping(value = CAR_MAKE + "/edit")
    public abstract void save(@Valid @RequestBody Serializable car)
}

And then an actual controller could look something like this:

@RestController
public class AudiController extends CarController {

    private AudiService audiService;

    @Autowired
    public AudiController(AudiService audiService){
        super("audi");
        this.audiService = audiService;
    }

    @Override
    public void save(@Valid @RequestBody Serializable car) {
        audiService.save((Audi) car);
    }
    .
    .
    .
}

The problem is that spring does not allow me to have the value for request mappings with a final variable if it is initialized through the constructor (if the CAR_MAKE is initialized right on the field declaration eg. protected final String CAR_MAKE = "s" it works). So is there any way to work around this so that the paths can come from each subclass?

Matt
  • 556
  • 8
  • 31
  • Well First the REST implementation is not correct. We should not have save,delete and edit in URI rather we should us right HTTP protocol with same URI so it performs different operation. Use PUT for edit, POST for save, DELETE for delete. – Sushil Behera Mar 08 '19 at 22:37
  • Second it should be the request payload which should drive which service you would like to call. – Sushil Behera Mar 08 '19 at 22:39

1 Answers1

0

Not near a compiler but something like this.

Implement a CarService interface:

public interface CarService {
  String getBrand();
  void save(Car car);
  // ...
}

Implement AudiCarService, BmwCarService (etc) types that implement CarService.

Implement a CarService repository something like:

public class CarServiceRepository {
    private Map<String, CarService> carServicesByBrand;

    public Optional<CarService> findFor(String brand) {
        return Optional.ofNullable(carServicesByBrand.get(brand));
    }

    @Autowired
    public void setCarServicesByBrand(List<CarService> carServices) {
        this.carServicesByBrand = carServices.stream().collect(Collectors.toMap(CarService::getBrand, Function.identity()));
    }
}

Implement a single controller "CarController":

@RequestMapping(value="/api/cars")
@Component
public class CarController {
    @Autowired
    private CarServiceRepository carServiceRepository;

    @PostMapping(value = "/{brand}/save")
    public void save(@Valid @RequestBody Serializable car, @PathParam String brand) {
        carServiceRepository.findFor(brand).ifPresent(carService -> carService.save(car));
    }

    // ...
}

Consider also favoring HTTP verbs over explicit verbs in URLs e.g. Why does including an action verb in the URI in a REST implementation violate the protocol?

Not a JD
  • 1,864
  • 6
  • 14