Репозиторий против DAO (снова)
В общем, эта история не имеет значения, но только для объяснения кода ниже:
Сервер обрабатывает пользователей и группы пользователей. Группы пользователей могут "обнаруживать" места - в данный момент эти места поступают исключительно из API Google Адресов.
Текущая реализация
В настоящее время у меня много JpaRepository
объекты, которые я называю Repository, в моем слое обслуживания. Я подчеркиваю "Репозиторий", потому что в предложенном ниже решении они будут понижены до DAO.
Однако, что мне не нравится в моем текущем коде, а также причина моего вопроса здесь - это количество репозиториев, которые можно найти в 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);
});
}
}
Решение, которое не работает
Что могло бы сделать этот код намного проще и могло бы решить эту проблему, было бы @Inheritance
:
@Entity
@Table(name = "place")
@Inheritance(strategy InheritanceType.JOINED)
public class Place { /* .. */ }
@Entity
@Table(name = "google_place")
public class GooglePlace extends Place { /* .. */ }
Тем не менее, это не вариант, потому что тогда я не могу иметь PlaceRepository
который экономит только место. Hibernate, похоже, не нравится.,
Мое предложение
Я думаю, что моя путаница начинается с имен, которые использует Spring. Например JpaRepository
- Я не уверен, действительно ли это "правильное" имя. Потому что, насколько я понял, эти объекты на самом деле работают как объекты доступа к данным (DAO). Я думаю, что это должно выглядеть примерно так:
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..
}
}
Резюме
Я хотел бы знать, что я не вижу. Я борюсь с фреймворком или моя модель данных не имеет смысла, и именно поэтому я сталкиваюсь с этим? Или у меня все еще есть проблемы с использованием двух шаблонов "Репозиторий" и "DAO"?
Как реализовать это?
2 ответа
Я бы сказал, что вы правы, что в вашем сервисе слишком много зависимостей репозитория. Лично я стараюсь сохранить количество @Autowired
зависимости до минимума, и я пытаюсь использовать хранилище только в одном сервисе и раскрыть его функциональность более высокого уровня через этот сервис. В нашей компании мы называем этот суверенитет данных (по-немецки: Datenhoheit), и его цель - обеспечить, чтобы в приложении было только одно место, где эти объекты были изменены.
Из того, что я понимаю из вашего кода, я хотел бы представить PlacesService
который имеет все зависимости от PlaceRepository
, GooglePlaceRepository
а также GooglePlaces
, Если вы считаете, что Сервис - это не то имя, которое вы можете назвать PlacesDao
, отметьте это с Весной @Component
аннотировать и внедрять все хранилища, которые по определению являются коллекциями вещей
@Component
public class PlacesDao {
@Autowired
private PlaceRepository placeRepository;
@Autowired
private GooglePlaceRepository googlePlaceRepository;
Эта служба /DAO может предложить API findPlacesForGroup(userGroup)
а также createNewPlace(...)
и, таким образом, сделать вашу петлю меньше и элегантнее.
На заметку: вы можете объединить свои первые четыре строки в одну. Java Optionals поддерживают orElseThrow()
метод:
UserGroup userGroup = userGroupRepository.findById(groupId).orElseThrow(() ->
new EntityNotFoundException(String.format("User group with id %s not found.", groupId));
Я думаю, что foreach не выглядит хорошим подходом для меня. Вы делаете многое для одной функции. Я бы переориентировал это на стандартный цикл.
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);
И эта часть: я не понимаю, почему вы это делаете. Вы можете просто обновить экземпляр googlePlace, загруженный из репозитория. Hibernate/ Сделки делают все остальное за вас.