In general this back-story does not matter but just to explain the code below:
The server handles users and user groups. User groups are able to "discover" places - at this point in time these places are coming exclusively from the Google Places API.
Current Implementation
Currently, I have a lot of JpaRepository
objects, which I call Repository, in my Service Layer. I am stressing "Repository" because in my proposed solution below, they'd be downgraded to DAOs.
However, what I do not like in my current code, and also the reason for my question here, is the amount of repositories one can find in the UserGroupService
.
@Service
public class UserGroupService {
private final static Logger LOGGER = LogManager.getLogger(UserGroupService.class);
@Autowired
private UserGroupRepository userGroupRepository;
@Autowired
private UserGroupPlaceRepository userGroupPlaceRepository;
@Autowired
private PlaceRepository placeRepository;
@Autowired
private GooglePlaceRepository googlePlaceRepository;
@Autowired
private GooglePlaces googlePlaces;
public UserGroupService() {
}
@Transactional
public void discoverPlaces(Long groupId) {
final UserGroup userGroup = this.userGroupRepository.findById(groupId).orElse(null);
if (userGroup == null) {
throw new EntityNotFoundException(String.format("User group with id %s not found.", groupId));
}
List<PlacesSearchResult> allPlaces = this.googlePlaces.findPlaces(
userGroup.getLatitude(),
userGroup.getLongitude(),
userGroup.getSearchRadius());
allPlaces.forEach(googlePlaceResult -> {
GooglePlace googlePlace = this.googlePlaceRepository.findByGooglePlaceId(googlePlaceResult.placeId);
if (googlePlace != null) {
return;
}
Place place = new Place();
place.setLatitude(googlePlaceResult.geometry.location.lat);
place.setLongitude(googlePlaceResult.geometry.location.lng);
place.setPlaceType(Place.PlaceType.GOOGLE_PLACE);
place.setName(googlePlaceResult.name);
place.setVicinity(googlePlaceResult.vicinity);
place = this.placeRepository.save(place);
UserGroupPlace.UserGroupPlaceId userGroupPlaceId = new UserGroupPlace.UserGroupPlaceId();
userGroupPlaceId.setUserGroup(userGroup);
userGroupPlaceId.setPlace(place);
UserGroupPlace userGroupPlace = new UserGroupPlace();
userGroupPlace.setUserGroupPlaceId(userGroupPlaceId);
this.userGroupPlaceRepository.save(userGroupPlace);
googlePlace = new GooglePlace();
googlePlace.setPlace(place);
googlePlace.setGooglePlaceId(googlePlaceResult.placeId);
this.googlePlaceRepository.save(googlePlace);
});
}
}
A Solution That Does Not Work
What could make this code a lot simpler and had the potential to resolve this mess up there, would be @Inheritance
:
@Entity
@Table(name = "place")
@Inheritance(strategy InheritanceType.JOINED)
public class Place { /* .. */ }
@Entity
@Table(name = "google_place")
public class GooglePlace extends Place { /* .. */ }
However, this is not an option because then I cannot have a PlaceRepository
which saves just a place. Hibernate does not seem to like it..
My proposal
I think my confusion starts with the names that Spring is using. E.g. JpaRepository
- I am not so sure if this is actually "the right" name. Because as far as I understood, these objects actually work like data access objects (DAOs). I think it should actually look something like this:
public interface PlaceDao extends JpaRepository<Place, Long> {
}
public interface GooglePlaceDao extends JpaRepository<Place, Long> {
}
@Repository
public class GooglePlaceRepository {
@Autowired
private PlaceDao placeDao;
@Autowired
private GooglePlaceDao googlePlaceDao;
public List<GooglePlace> findByGroupId(Long groupId) {
// ..
}
public void save(GooglePlace googlePlace) {
// ..
}
public void saveAll(List<GooglePlace> googlePlaces) {
// ..
}
}
@Service
public class UserGroupService {
@Autowired
private GooglePlaceRepository googlePlaceRepository;
@Autowired
private UserGroupRepository userGroupRepository;
@Transactional
public void discoverPlaces(Long groupId) {
final UserGroup userGroup = this.userGroupRepository.findById(groupId).orElse(null)
.orElseThrow(throw new EntityNotFoundException(String.format("User group with id %s not found.", groupId)));
List<PlacesSearchResult> fetched = this.googlePlaces.findPlaces(
userGroup.getLatitude(),
userGroup.getLongitude(),
userGroup.getSearchRadius());
// Either do the mapping here or let GooglePlaces return
// List<GooglePlace> instead of List<PlacesSearchResult>
List<GooglePlace> places = fetched.stream().map(googlePlaceResult -> {
GooglePlace googlePlace = this.googlePlaceRepository.findByGooglePlaceId(googlePlaceResult.placeId);
if (googlePlace != null) {
return googlePlace;
}
Place place = new Place();
place.setLatitude(googlePlaceResult.geometry.location.lat);
place.setLongitude(googlePlaceResult.geometry.location.lng);
place.setPlaceType(Place.PlaceType.GOOGLE_PLACE);
place.setName(googlePlaceResult.name);
place.setVicinity(googlePlaceResult.vicinity);
googlePlace = new GooglePlace();
googlePlace.setPlace(place);
googlePlace.setGooglePlaceId(googlePlaceResult.placeId);
return googlePlace;
}).collect(Collectors.toList());
this.googlePlaceRepository.saveAll(places);
// Add places to group..
}
}
Summary
I would like to know what I don't see. Am I fighting the framework, or does my data model not make sense and this is why I find myself struggling with this? Or am I still having issues on how the two patterns "Repository" and "DAO" are supposed to be used?
How would one implement this?