Давно не было пет-проектов. Вот Swiply - полноценный клон Tinder. Это Android-приложение с серверно-клиентной архитектурой и бэкендом на Spring Boot. Я повторил почти весь функционал дейтинг-приложений: свайпы, матчи, профили по интересам, работа с геолокацией, активный чат с использованием Redis и безопансостей (sic), сложная авторизация через JWT, модерацию, что тоже важно. Реализовал сразу две БД, что для меня впервые - PostgreSQL и MongoDB. Статусы чата и то, что очень для меня важно: я впервые сделал не монолитные сервисы, а модульный бэкенд, и что еще сложнее - модульный Android. Android на Jetpack Compose, архитектура MVVM. Вот полный стек.
Android — Compose, Material 3, MVVM, многомодульный Gradle (13 модулей), Coroutines/Flow, Hilt, Retrofit, свой STOMP-клиент на OkHttp, Room + Paging 3, DataStore, шифрованное хранилище токенов, FusedLocationProvider, Navigation Compose, кастомные Canvas-иллюстрации. Тесты — JUnit4, MockK, Turbine, Compose UI Test.
Бэкенд — Spring Boot 3, suspend-контроллеры, Spring Security с JWT, JPA, Spring Data MongoDB и Redis, Spring AMQP, WebSocket/STOMP, Thymeleaf, Flyway, springdoc-openapi. Тесты — JUnit 5, MockK, Testcontainers.
Инфраструктура PostgreSQL 16 + PostGIS, MongoDB 7, Redis 7, RabbitMQ с DLQ, MinIO, Docker Compose, GitHub Actions.
Архитектурные решения (ADR-лог)
D1. PostgreSQL + MongoDB вместе. Из ТЗ: Postgres (+PostGIS) источник истины для всего транзакционного; MongoDB только conversations/messages.
D2. geometry(Point,4326) вместо geography в колонке. Hibernate Spatial валидирует geometry-тип без сюрпризов; метровая семантика достигается кастом ::geography в каждом гео-запросе (ST_DWithin, ST_Distance). Поведение идентично, схема дружит с ddl-auto: validate.
D3. EXIF-стрипинг через полный переэнкод. Вместо вырезания EXIF-тегов изображение декодируется и собирается заново (Thumbnailator → JPEG). Метаданные (включая GPS) исчезают by construction, а «полиглоты» (файлы с двойным дном) не переживают переэнкод. Принимаются только JPEG/PNG по magic bytes.
D4. Suspend-контроллеры + блокирующие сервисы. Spring MVC поддерживает suspend-хендлеры; JPA остаётся блокирующим, поэтому паттерн: suspend fun → withContext(Dispatchers.IO) { транзакционный сервис }. @Transactional на suspend-функциях с JPA не работает — транзакции живут только внутри блокирующего слоя.
D5. ASYNC-dispatch разрешён в Spring Security. Suspend-контроллеры завершаются через ASYNC-dispatch, где OncePerRequestFilter (JWT) по умолчанию не выполняется → AuthorizationFilter видел анонима и валил запрос 401-м. Решение из документации: dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() — аутентификация уже проверена на исходном REQUEST-dispatch.
D6. Установка SecurityContext — только через createEmptyContext() + setContext(). Мутация deferred-контекста (getContext().authentication = …) в Spring Security 6 теряется.
D7. Нормализация пары мэтча — лексикографически по uuid.toString(). UUID.compareTo в Java сравнивает знаково и НЕ совпадает с порядком uuid в PostgreSQL; строковое сравнение hex-представления совпадает с байтовым порядком БД, на котором держится CHECK-констрейнт user_a_id < user_b_id.
D8. Advisory-lock пары при свайпе. pg_advisory_xact_lock(hashtextextended(a:b)) сериализует встречные лайки — дубль мэтча невозможен даже при гонке.
Более тг не дает написать(((
Более подробно в https://github.com/ventilia/Swiply









