Spring Pet Clinic: So Far

Spring Pet Clinic: So Far
Photo by Mikhail Vasilyev / Unsplash

I'm putting together my own implementation of the Spring Pet Clinic, which is a project by the Spring team to demonstrate the capabilities of Spring. As a relative newcomer to Spring and to paradigms common in Java, I'm trying to get underneath the covers of Spring and at the same time retooling some habits that I've developed from learning my craft on Rails through a bootcamp.

(For the record, I like Rails, and I like it a lot. I have zero qualms about the quality of my bootcamp education. The simple reality of bootcamps is that there is no time in the condensed bootcamp format to give too much thought to architectural considerations, so you're given a basic recipe for building an MVC backend according to Rails conventions and told to follow it. Naturally, there will be a ton of room for refinement, but that comes afterwards, once you've learnt the basic tools of the trade. What I'm working on now is expanding my very basic toolbox and refining my use of the tools I do have.)

This is what my models look like:

UML diagram of my Spring Pet Clinic implementation

(I took a detour from building Spring Pet Clinic to do a UML course on Pluralsight while I was putting this diagram together, so that's one thing I've done today.)

All the models inherit from a BaseEntity class, which provides the id attribute and getters and setters for id. Both BaseEntity and Person are annotated @MappedSuperclass, so their children inherit their attributes, but they themselves do not have corresponding tables in the database.

The view in this application is a JSON response that is consumed by a React frontend.

The first user story I decided to take on was "As a user, I can see a list of vets practising at the clinic, so that I can make an informed choice about my pet's vet care." Even as I type this user story, I realise there is room for improvement: what does an informed choice look like? How does seeing a list of vets inform this choice? And from an XD's perspective: What information does this list require and why? Names for sure, but what about headshots? What is the user looking for when they see this list: do they want to sort the vets by some criteria (specialty, seniority, years of experience), do they want to quickly determine what specialties this clinic is strongest in, do they want to get a feel for the vet who will be treating their pet?

Because this project is specifically about deepening my knowledge of Spring, I decided the goal at this stage is to simply produce a table of the vets and their specialties. I put together a simple React app that makes an API call to the GET /vets endpoint. On the backend, starting from the controller, I had a very standard @RestController:

@RestController
@RequestMapping("vets")
public class VetController {

	@Autowired
	VetService vetService;

	@GetMapping
	@CrossOrigin(origins = "http://localhost:3000")
 	public List<Vet> getAllVets() {
 		return vetService.getAllVets();
	}
}

The VetService, at this stage in the application, has just one method that is a simple wrapper around a repository:

@Service
public class VetService {

	VetRepository vetRepository;

	public VetService(VetRepository vetRepository) {
		this.vetRepository = vetRepository;
	}

	public List<Vet> getAllVets() {
		return vetRepository.findAll();
	}
}

And the VetRepository is a straightforward JpaRepository:

public interface VetRepository extends JpaRepository<Vet, Long> {
}

Now we get to the interesting part. Here's my Vet entity:

@Entity
public class Vet extends Person {

	@ManyToOne
	@JoinColumn(nullable = false)
	private Specialty specialty;

	// other relationships that are not relevant to this discussion

	// constructors, getters, setters, etc.
}

And the Specialty entity:

@Entity
public class Specialty extends BaseEntity {

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

	@OneToMany(mappedBy = "specialty")
	Set<Vet> vets = new HashSet<>();

	// constructors, getters, setters, etc.
}

And of course, I had a test:

@SpringBootTest(webEnvironment = RANDOM_PORT)
@ExtendWith(MockitoExtension.class)
class VetControllerTest {

	List<Vet> vets;

	@Autowired
	TestRestTemplate testRestTemplate;

	@MockBean
	VetService vetService;

	@BeforeEach
	void setUp() {
		// add some vets to the vets list here
	}

	@Test
	void testGetAllVets() {
		when(vetService.getAllVets()).thenReturn(vets);
		var responseType = new ParameterizedTypeReference<List<Vet>>() {};
		ResponseEntity<List<Vet>> response = testRestTemplate.exchange("/vets", HttpMethod.GET, HttpEntity.EMPTY, responseType);
		List<Vet> actualVets = response.getBody();

		assertNotNull(actualVets);
		assertEquals(vets.size(), actualVets.size());
		assertTrue(vets.containsAll(actualVets));
		assertTrue(actualVets.containsAll(vets));
	}
}

I took some code from a TDD workshop I attended a few weeks ago, and this test passes. (Whether it is the best way to test a controller, as opposed to using MockMvc, is a question I still don't know how to answer. It's on my list of things to figure out.)

However, calling the API endpoint reveals a big problem:

[
  {
    "id": 1,
    "firstName": "Chris",
    "lastName": "Brown",
    "specialty": {
      "id": 1,
      "name": "General",
      "vets": []
    },
    "fullName": "Chris Brown"
  }
]

A Vet has one Specialty. A Specialty has many Vets. The Specialty object itself includes a list of Vets belonging to that specialty. Fortunately for the API response, the specialty.vets array is empty at the moment, although I don't fully understand why it hasn't become a circular reference.

The easiest fix is to remove the @OneToMany relationship from the Specialty class. This makes the relationship unidirectional: Vet knows about Specialty, but Specialty does not know about Vet. However, I also want to create an endpoint GET /vets/specialty/{specialtyName} that returns all the vets that belong to a particular specialty, so Specialty should know about Vet. Making the relationship unidirectional is a cop-out and doesn't solve the underlying problem with the structure of this code.

Fortunately, I remembered an applicable lesson from the TDD workshop: decouple an entity from its representation.

I must admit, this is not a problem I ever thought about in Rails. In a stock Rails setup, jbuilder provides a mechanism to decouple models from their JSON representation without a purpose-built intermediate object to serve as a mapper (is it a mapper, or is there a more specific name for this pattern?). Now I need to learn the Java/Spring paradigm for doing this.

Next Steps

  1. Determine what the API response for GET /vets should look like.
  2. Rewrite the VetControllerTest so that I'm testing against the JSON, not against the deserialized Vet objects
  3. Create an object VetListView or something similar whose sole responsibility is to convert List<Vet> into the JSON representation I want.

With all that said... I have something completely different I need to work on for the time being, so I may not revisit the Spring Pet Clinic for a while.