Search code examples
springrestspring-datadomain-driven-designspring-data-rest

Adding fields to an object returned by Spring Data Rest endpoint?


I have a Car entity. I want to expose its data in a REST API. I'm currently using Spring Data REST to do this. After reading this post, I became comfortable doing this instead of creating a CarDto and passing an instance of that back to the client:

The Car Entity

@Entity
@Data
public class Car {

  @Id
  private Long id;
  
  private BigDecimal askingPrice;
  
  // other fields

}

Spring Data Repository

public interface CarRepository extends CrudRepository<Car, Long> {
}

Gradle file has the following dependency to enable clients to access Cars at /api/cars

implementation 'org.springframework.boot:spring-boot-starter-data-rest'

Now, I have a new Car property. Its value is not stored in the same database as Car entity. I get its value from a web service. I seem to be in a similar situation that is described in this post that advocates the use of DTOs.

Do I need to abandon Spring Data REST?

I know that I can modify my Car entity to include this new field as a transient...

@Entity
@Data
public class Car {

  @Id
  private Long id;
  
  private BigDecimal askingPrice;
  
  // Value is populated from a web service
  @Transient      
  private BigDecimal kellyBlueBookPrice;

  // other fields

}

But is that a good approach? Is there a way to have Spring Data REST return some form of a DTO that includes this new property? It's possible that a few other fields may be added in the future from other data sources.


Solution

  • Is there a way to have Spring Data REST return some form of a DTO that includes this new property?

    No way directly you can achieve this. But you need to write some code where it can get data and convert it to DTO. Also using this approach URL will be not the same it will be changed as if you still using spring-data-rest.

    There is a sample given at https://stackoverflow.com/a/45401735. But it's using the old version and is not compatible with the new spring-data-rest.

    The best would be to use DTO and not directly use spring data rest implementation if you have changes in response. Some of the scenarios you can consider:

    • If you using ManyToMany, ManyToOne, and OneToOne mapping then you must skip bidirectional mapping. If you have then while converting the entity to JSON it will go in a circular loop and will never come back.
    • With using mapping it requires the hibernate session to be open until it converts the entity to JSON.
    • With Entity expose it will also expose all and other related information which is not required. Which can lead to Penetration Test to be failed for your application.
    • To partially save data you still need to pass the full object else it will reset other properties to null.

    To avoid boilerplate code converting Entity to DTO and vice-versa. You can use some libraries like MapStruct, ModelMapper.

    MapStruct example

    @Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
    public abstract class CarMapper {
    
        public abstract CarDto carToCarDto(Car car);
    
        @InheritInverseConfiguration
        public abstract Car carDtoToCar(CarDto carDto);
    
    }
    

    By using DTO you can have a separate service layer where you can manage database transactions to do operations in multiple tables. You can control how much information is exposed through REST.

    If you want to use the same response for operations then you can use spring-hateoas which will have _embedded/_links object.