Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OPEN API / DTO generation #52

Merged

Conversation

alexandre-touret
Copy link
Contributor

@alexandre-touret alexandre-touret commented Sep 1, 2021

Purpose

Closes #35

How is it done?

Using Open Api Generator in the build phase using maven.
The file api-docs.yml is processed during the build phase by this plugin and then generates the source files in the target/generated-sources/openapi folder.

The mapping with the entities is made by mapstruct.

Validation

All the unit & integration tests have been successfully executed .

@alexandre-touret alexandre-touret changed the title Moving to springdoc dependency Move to springdoc dependency Sep 1, 2021
@arey
Copy link
Member

arey commented Sep 2, 2021

What are the benefits of moving from springfox to springdoc?
The idea behind #35 is to switch toward the contract-first approach: write the openAPI specification by hand then generate the DTO with a plugin like openapi-generator-maven-plugin

@alexandre-touret
Copy link
Contributor Author

Hello @arey
The only benefit I could find is the open api 3 support.
If you prefer, I can stay on Springfox. Tell me.

I didn't get you main idea behind this issue.
I will integrate it.

@alexandre-touret
Copy link
Contributor Author

Hello @arey
I have a question about the contract-first approach to apply in this PR: Would you generate all of your existing DTOs or new ones ?
For the first possibility, I saw in the code that you directly expose your entities. In this kind of new approach, it would be nice to separate the entities from the DTOs and to map them ( for instance with mapstruct).

@arey
Copy link
Member

arey commented Sep 3, 2021

Hi @alexandre-touret
Yes we do not should expose directly the JPA entities. You may generate versioned DTO from the OpenAPI spec then map them with MapStruct. That's what we do in my company.
I remember the fork https://github.com/gantsign/spring-petclinic-openapi is using this approach.

Do you think we could keep the same contract? We have several petclinic forks (ie the Angular one) that are based upon.

@alexandre-touret
Copy link
Contributor Author

I do the same in my company.
I think we could keep the same contract.

Add minimal mapstruct configuration

BREAKING CHANGE: the springdoc dependency is incompatible with this
plugin. I rollbacked to springfox/swagger
@alexandre-touret alexandre-touret changed the title Move to springdoc dependency Add OPEN API / DTO generation Sep 13, 2021
@alexandre-touret
Copy link
Contributor Author

Hello @arey
Currenlty, I have an issue wile generating DTO containing a Date field

If I understood, your date format is yyyy/MM/dd

The openapi generator does't add any JsonFormat annotation. When I try to run the existing unit tests I always have a serialization issue because this date pattern is not clearly recognized.

Currently it works because you previously used this annotation.

My question is : is it possible to change the date format to yyyy-MM-dd for instance ?

@arey
Copy link
Member

arey commented Sep 13, 2021

In the JacksonCustomVisitDeserializer I see usage of the yyyY/MM/dd pattern.
It does not respect the RFC-3339 https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 used by the OpenAPI specification. Thus yes, I propose to change the date format to yyyy-MM-dd. Bye bye backward compatibility.

@alexandre-touret
Copy link
Contributor Author

Hello,
I'll try to fix this issue in order to ensure backward compatibility.
If after few days sealing with this issue, I'm still stuck, I will change the date format.

@vfedoriv
Copy link
Collaborator

@alexandre-touret as I recall, springfox also doesn't support this date format
#20 (comment)
so feel free to change it

@vfedoriv
Copy link
Collaborator

UPD.
The one problem - after we change the date format, we may have issues with front-end apps that use this app as a back-end.
"Canonical" way to do this changes and preserve backward compatibility - to provide new API version "/v2"

@arey
Copy link
Member

arey commented Sep 21, 2021

In the real world, providing a v2 version with dual run will be the solution. But for a sample it's too much work. Maybe just changing the API path to /v2 in order the client break earlier and add a changelog in the readme?
We could migrate the angular fork to this new version.

@alexandre-touret
Copy link
Contributor Author

Hello,
Sorry for the late answer. I' ve been busy these days...
I have another issue regarding this update:

There are an infinite recursion with the generated classes.
The generated DTOs provide a bidirectionnal relationship .
I saw this issue was fixed using the custom de/serializers. It's not possible using this approach.

If I fixed this issue, I would modify the DTOs and add another backward regression

I then have a "simple" question : does it worth?

Regards

@arey
Copy link
Member

arey commented Sep 29, 2021

Hi Alexandre,

Sorry for the delay. By bidirectionnal relationship, did you mean the reference of the id of parent object like this pet referencing its owner?

PetType petType = pet.getType();
jgen.writeObjectFieldStart("type");
jgen.writeNumberField("id", petType.getId());
jgen.writeStringField("name", petType.getName());
jgen.writeEndObject(); // type

if (pet.getOwner().getId() == null) {
    jgen.writeNullField("owner");
} else {
    jgen.writeNumberField("owner", pet.getOwner().getId());
}

Personally, I don't think it's helpful. An acyclic graph is often preferable.
I don't know if this particularity is used in the the angular client. @simrin051, @vfedoriv what is your advice?

@alexandre-touret
Copy link
Contributor Author

Hi,
Sorry for the delay too...

When I mentionned bidirectionall relationship, I meant this kind of error produced by the OwnerRestControllerTests


com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"]->org.springframework.samples.petclinic.dto.OwnerDto["pets"]->java.util.ArrayList[0]->org.springframework.samples.petclinic.dto.PetDto["owner"])

	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
	at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
	at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
	
	

When I asked you for knowing if it is workth, I would like to know if this whole functionality worths breaking the backward compatibility.
The date format would be indeed changed and the DTOs would be deeply modified if we add the DTO generation functionaliy.

IMO, it doesn't worth.
Up to you to say if I should continue or not.

@arey
Copy link
Member

arey commented Oct 3, 2021

In order to get closer to the standards, I think you can go on. We may add a changelog to the readme. And I'm sure that @simrin051 or me could migrate the Spring Petclinic Angular fork.

Does your OpenAPI specs looks like the one petclinic.openapi.yaml coded by @freemanjp ?

@alexandre-touret
Copy link
Contributor Author

I generated the openapi description file from the current REST API.
You can get it here

@arey
Copy link
Member

arey commented Oct 5, 2021

The generated file seems to require some little rework.
For instance, the pet's birthdate should have a date format instead of a date-time one.

 "birthDate" : {
            "type" : "string",
            "format" : "date-time"
          },

Same remark for the visit date.
Later, we could add some details: pattern, min / max length, example, description and so son.

In the given yaml, the editable fields are factorized in a xxxxFields type (ie. VisitFields) and use in conjonction with the allOf keyword :

 Visit:
      title: Visit
      description: A booking for a vet visit.
      allOf:
        - $ref: '#/components/schemas/VisitFields'
        - type: object
          properties:
            id:
              title: ID

I don't know if it's a good practice or not.

@alexandre-touret
Copy link
Contributor Author

Hi @arey ,
you can review this PR if you want.
If you want I can squash all the commits in one.

Copy link
Member

@arey arey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job Alexandre

pom.xml Outdated Show resolved Hide resolved
pom.xml Outdated Show resolved Hide resolved
pom.xml Show resolved Hide resolved
pom.xml Show resolved Hide resolved
pom.xml Show resolved Hide resolved
@arey arey merged commit 5a35697 into spring-petclinic:master Nov 11, 2021
@alexandre-touret alexandre-touret deleted the feature/open-api-documentation branch November 11, 2021 15:07
haraldreingruber-dedalus pushed a commit to haraldreingruber-dedalus/spring-keycloak-angular-exercise that referenced this pull request Sep 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Migrate to OpenAPI Specification
3 participants