Skip to content

Add friends likes#2

Merged
CrodiYa merged 35 commits intomainfrom
add-friends-likes
Nov 1, 2025
Merged

Add friends likes#2
CrodiYa merged 35 commits intomainfrom
add-friends-likes

Conversation

@CrodiYa
Copy link
Owner

@CrodiYa CrodiYa commented Oct 29, 2025

Вопросы/Проблемы

  • Основная проблема с сервисами - по большей части они классы-обертки и не имеют своей логики, лишь вызывают методы хранилища. Но зато эту логику можно добавить при надобности, не меняя хранилище.
  • В основном коде нет аннотаций Autowired, так как в моем понимании её следует избегать в проде. Зависимости внедряются через конструкторы, современный спринг-бут вроде и не требует аннотацию.
  • У хранилища есть метод throwIfNotFound - удобный метод, бросающий исключение, если сущность не найдена. Его пришлось переопределить для наследников, чтобы сообщение исключения соответствовало классу. Это оказался самый простой способ заменить одно слово=)
  • Я использую ConcurrentHashMap для потокобезопасности и отсутствия блокировки, но только потому что вычитал это где-то на stackOverflow. Насколько я понимаю лучше использовать их для серверов? Технически возможно использовать и обычный HashMap, но тогда каждый запрос будет занимать больше времени?
  • Теоретически можно улучшить поиск топа фильмов и сразу хранить их отсортированными в дереве, но я не уверен, что следует, поэтому использую streamAPI и компаратор.

Буду очень рад услышать предложения по улучшению для этих поинтов(да и для других, если они есть)!

Всякое

  • Удалил кастомную аннотацию ValidBirthday и соответствующие классы. Заменил на @PastOrPresent.
  • Добавил логи zalando.logbok и профиль для дебага.

Хранилище:

Основная идея - сохранить классы User и Film как можно более простыми, поэтому они получили только наследование от абстрактного класса StorageData. Film получил поле Long likes.
Хранилище реализовано при помощи интерфейсов и абстрактного класса (на схеме)
Хранилище

  • InMemoryUserStorage хранит друзей каждого пользователя в виде Map<userId, Map<friendId, User>>.
  • InMemoryFilmStorage хранит лайки каждого пользователя в виде Map<filmId, Set<userId>>.
    Немного требовательно, зато удобно.
    Идея, где при каждом возврате пользователя/фильма мне одновременно приходит список друзей/пользователей который поставили лайки кажется мне странной.

Сервисы

  • За операции с друзьями отвечает FriendShipService - он вызывает методы хранилища и имеет свою логику.

  • За операции с пользователями отвечает UserService - он вызывает метода хранилища и методы FriendShipService - по большой части просто класс-обертка.

  • За операции с лайками отвечает FilmLikeService- он вызывает методы хранилища и имеет свои проверки(например проверку существования пользователя).

  • За операции с пользователями отвечает FilmService - он вызывает метода хранилища и методы FilmLikeService - по большой части просто класс-обертка.

Контроллеры

Знают только о своих сервисах. выполняют проверки через аннотации.

Тесты

Постарался улучшить качество и количество тестов при помощи mockito, для сервисов почти нет тестов, так как тестировать особо нечего

По неведомой мне причине в прошлый раз я начал писать javadoc на английском, так что пришлось поддерживать эту тенденцию.

…ler should not have. All operations go through UserService. Add new endpoints.
…ler should not have. All operations go through FilmService. Add new endpoints.

/**
* Private constructor to initialize ID generator with starting ID = 1.
* Constructor for dependency injection

Choose a reason for hiding this comment

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

Вместо многострочного комментария лучше использовать Java Docs

*/
@PutMapping
public User updateUser(@Valid @RequestBody User newUser) {
if (newUser.getId() == null) {

Choose a reason for hiding this comment

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

Этой проверке тоже н место в слое контроллера, всё убираем в сервисный слой.

@NoArgsConstructor
@AllArgsConstructor
public class Film {
public class Film extends StorageData {

Choose a reason for hiding this comment

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

Не нужное усложнение с наследованием от StorageData
Тут не прослеживается семейства, между фильмом и пользователем который его смотрит, то-есть наследование излишне.

@AllArgsConstructor
@NoArgsConstructor
public abstract class StorageData {
protected Long id;

Choose a reason for hiding this comment

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

Как я и сказал выше, излишний родитель ещё и только с идентификатором.

* @see UserStorage
*/
@Service
public class FilmLikeService {

Choose a reason for hiding this comment

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

Сервисный слой лучше сделать через интерфейсы используя всю силу полиморфизма.


public interface BasicStorage<T extends StorageData> {

T add(T t);

Choose a reason for hiding this comment

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

в результате тут свели преимущество java в жесткой типизации к минимуму, теперь имеем определение типа на уровне вызова методов, что крайне не удобно.


@Override
public User add(User user) {
User returnUser = super.add(user);

Choose a reason for hiding this comment

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

метод выполняет разную логику а заявлено что это репозиторий, то-есть тут должно быть только добавление и ничего больше, вся остальная логика должна находится в сервисном слое.


@Override
public User remove(Long id) {
throwIfNotFound(id);

Choose a reason for hiding this comment

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

Тут аналогично, и проверяем наличие, и кидаем ошибку, и создаём коллекции наполняя её, и логика работы в цикле со своей логикой лямбда выражения, в общем опять слишком много действий для класса репозитория и метода удаления, он должен просто удалить, всё остальное не должно быть в этом слое, это задача сервисного слоя.


@Override
public void clear() {
super.clear();

Choose a reason for hiding this comment

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

В общем такая проблема везде.

}

@Test
public void shouldThrowNotFoundWhenUserIsNotFoundReturnFriends() {

Choose a reason for hiding this comment

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

в каких то тестах есть модификаторы доступов у методов в каких то нет, это плохо, нужно привести к единообразию. Либо везде пишем либо везде не пишем, лучше писать.

@CrodiYa CrodiYa merged commit 1efef02 into main Nov 1, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments