spring-projects / spring-data-rest

Simplifies building hypermedia-driven REST web services on top of Spring Data repositories
https://spring.io/projects/spring-data-rest
Apache License 2.0
916 stars 562 forks source link

Polymorphic projections for associations [DATAREST-783] #1156

Open spring-projects-issues opened 8 years ago

spring-projects-issues commented 8 years ago

Mikhail Kadan opened DATAREST-783 and commented

Consider the following case:

A Book has multiple authors, each of them may be either a Professional or an Amateur, e.g.:

class Book { String title; @OneToMany Set<Author> authors; }

class Person { String name; int age; }

class Author extends Person { @ManyToOne Book book; String licenseId; }

class AmateurAuthor extends Author { String favoriteColor; }

class ProfessionalAuthor extends Author { int professionalExperience; }

Corresponding repositories:

interface BookRepository {}

interface AmateurAuthorRepository {}

interface ProfessionalAuthorRepository {}

And the following projections:

@Projection(name = "default", types = Book.class)
public interface BookProjection { String getTitle(); Set<AuthorProjection> getAuthors(); }

public interface PersonProjection { String getName(); }

public interface AuthorProjection extends PersonProjection { String getLicenseId(); }

@Projection(name = "default", types = AmateurAuthor.class)
interface AmateurAuthorProjection extends AuthorProjection { String getFavoriteColor(); }

@Projection(name = "default", types = ProfessionalAuthor.class)
public interface ProfessionalAuthorProjection extends AuthorProjection { int getProfessionalExperience(); }

What do we have there is a Book having a set of Authors, each of them may be either an AmateurAuthor or a Professional Author.

What do I expect to get when I ask for a book resource/collection with a "default" projection:

... {
  "title": "Book #1",
  "authors": {
    "_embedded": {
      "professionals": [{"name": "Name #1", "licenseId": "License #1", professionalExperience": 10, "_links": {...}}, ...]
      "amateurs": [{"name": "Name #2", "licenseId": "License #2", "favoriteColor": "pink", "_links": {...}}, ...]
    }
  }
} ...

But that's what I actually get:

... {
  "title": "Book #1",
  "authors": [
    {"name": "Name #1", "licenseId": "License #1", "_links": {...}}, ...
    {"name": "Name #2", "licenseId": "License #2", "_links": {...}}, ...
  }]
} ...

As you see, I have no info on the type of an author and I don't get any additional properties defined for their polymorphic projections. That means only the base projection (AuthorProjection), and not inherited projections (AmateurAuthorProjection, ProfessionalAuthorProjection) is considered when constructing the response.

That could be future proved by defining:

@Projection(name = "default2", types = Book.class)
public interface BookProjection2 { String getTitle(); Set<PersonProjection> getAuthors(); }

And then requesting the "default2" provides:

... {
  "title": "Book #1",
  "authors": [
    {"name": "Name #1", "_links": {...}}, ...
    {"name": "Name #2", "_links": {...}}, ...
  }]
} ...

Another approach I tried was:

@Projection(name = "default3", types = Book.class)
public interface BookProjection3 { String getTitle(); Set<Author> getAuthors(); }

But that way I get no projection for "authors" at all:

... {
  "title": "Book #1",
  "authors": [
    {"name": "Name #1", "age": 10, "licenseId": "License #1", "professionalExperience": 10}, ...
    {"name": "Name #2", "age": 10, "licenseId": "License #1", "favoriteColor": "pink"}, ...
  }]
} ...

As for me this relates somehow to the https://jira.spring.io/browse/DATAREST-464 I have also included a sample project to demonstrate this issue


Affects: 2.4.2 (Gosling SR2)

Attachments:

1 votes, 1 watchers

spring-projects-issues commented 8 years ago

Mikhail Kadan commented

Actually you don't even need a 1-N association to reproduce that, 1-1 has the same behaviour as well