Implement MongoDB persistence adapters for application domains

- Create Spring Data Mongo entities and repositories for User, Session, WorkoutPlan, WorkoutDay, and WorkoutExercise
- Add mappers to handle conversions between domain models and persistence entities
- Create persistence adapters implementing the save and load output ports for each domain
- Replace empty MongoAdapter placeholder files with fully functional PersistenceAdapter classes
- Remove unused UUID import from the User domain model
This commit is contained in:
João P.A Silveira 2026-06-04 13:02:31 -03:00
parent e52a07dd9a
commit 5c1b7bcdd2
24 changed files with 481 additions and 5 deletions

View file

@ -1 +0,0 @@
package br.dev.jsilveira.coresync.session.adapter.out.persistence

View file

@ -0,0 +1,34 @@
package br.dev.jsilveira.coresync.session.adapter.out.persistence
import br.dev.jsilveira.coresync.session.adapter.out.persistence.mapper.SessionMapper
import br.dev.jsilveira.coresync.session.adapter.out.persistence.repository.SessionRepository
import br.dev.jsilveira.coresync.session.application.port.out.LoadUserWorkoutSessionPort
import br.dev.jsilveira.coresync.session.application.port.out.SaveUserWorkoutSessionPort
import br.dev.jsilveira.coresync.session.domain.exception.UserWorkoutSessionException
import br.dev.jsilveira.coresync.session.domain.model.UserWorkoutSession
import org.springframework.stereotype.Component
@Component
class SessionPersistenceAdapter implements SaveUserWorkoutSessionPort, LoadUserWorkoutSessionPort {
private final SessionRepository repository
private final SessionMapper mapper = new SessionMapper()
SessionPersistenceAdapter(SessionRepository repository) {
this.repository = repository
}
@Override
void save(UserWorkoutSession session) {
def entity = mapper.toEntity(session)
repository.save(entity)
}
@Override
UserWorkoutSession load(UUID id) {
def entity = repository.findById(id)
.orElseThrow({ new UserWorkoutSessionException("Sessão de treino não encontrada: ${id}") })
return mapper.toDomain(entity)
}
}

View file

@ -0,0 +1,14 @@
package br.dev.jsilveira.coresync.session.adapter.out.persistence.entity
import org.springframework.data.mongodb.core.mapping.Document
import java.time.LocalDateTime
@Document("workout_sessions")
record SessionEntity(
UUID id,
UUID userId,
UUID workoutDayId,
LocalDateTime startedAt,
LocalDateTime completedAt
) {}

View file

@ -0,0 +1,26 @@
package br.dev.jsilveira.coresync.session.adapter.out.persistence.mapper
import br.dev.jsilveira.coresync.session.adapter.out.persistence.entity.SessionEntity
import br.dev.jsilveira.coresync.session.domain.model.UserWorkoutSession
class SessionMapper {
SessionEntity toEntity(UserWorkoutSession domain) {
return new SessionEntity(
domain.id(),
domain.userId(),
domain.workoutDayId(),
domain.startedAt(),
domain.completedAt()
)
}
UserWorkoutSession toDomain(SessionEntity entity) {
return new UserWorkoutSession(
entity.id(),
entity.userId(),
entity.workoutDayId(),
entity.startedAt(),
entity.completedAt()
)
}
}

View file

@ -0,0 +1,7 @@
package br.dev.jsilveira.coresync.session.adapter.out.persistence.repository
import br.dev.jsilveira.coresync.session.adapter.out.persistence.entity.SessionEntity
import org.springframework.data.mongodb.repository.MongoRepository
interface SessionRepository extends MongoRepository<SessionEntity, UUID> {
}

View file

@ -1,2 +0,0 @@
package br.dev.jsilveira.coresync.user.adapter.out.persistence

View file

@ -0,0 +1,34 @@
package br.dev.jsilveira.coresync.user.adapter.out.persistence
import br.dev.jsilveira.coresync.user.adapter.out.persistence.mapper.UserMapper
import br.dev.jsilveira.coresync.user.adapter.out.persistence.repository.UserRepository
import br.dev.jsilveira.coresync.user.application.port.out.LoadUserPort
import br.dev.jsilveira.coresync.user.application.port.out.SaveUserPort
import br.dev.jsilveira.coresync.user.domain.exception.UserException
import br.dev.jsilveira.coresync.user.domain.model.User
import org.springframework.stereotype.Component
@Component
class UserPersistenceAdapter implements SaveUserPort, LoadUserPort {
private final UserRepository repository
private final UserMapper mapper = new UserMapper()
UserPersistenceAdapter(UserRepository repository) {
this.repository = repository
}
@Override
void save(User user) {
def entity = mapper.toEntity(user)
repository.save(entity)
}
@Override
User load(UUID id) {
def entity = repository.findById(id)
.orElseThrow({ new UserException("Usuário não encontrado: ${id}") })
return mapper.toDomain(entity)
}
}

View file

@ -0,0 +1,17 @@
package br.dev.jsilveira.coresync.user.adapter.out.persistence.entity
import org.springframework.data.mongodb.core.mapping.Document
import java.time.LocalDateTime
@Document("users")
record UserEntity(
UUID id,
String name,
String email,
Boolean emailVerified,
String image,
LocalDateTime createdAt,
LocalDateTime updatedAt,
Boolean isActive
) {}

View file

@ -0,0 +1,32 @@
package br.dev.jsilveira.coresync.user.adapter.out.persistence.mapper
import br.dev.jsilveira.coresync.user.adapter.out.persistence.entity.UserEntity
import br.dev.jsilveira.coresync.user.domain.model.User
class UserMapper {
UserEntity toEntity(User domain) {
return new UserEntity(
domain.id(),
domain.name(),
domain.email(),
domain.emailVerified(),
domain.image(),
domain.createdAt(),
domain.updatedAt(),
domain.isActive()
)
}
User toDomain(UserEntity entity) {
return new User(
entity.id(),
entity.name(),
entity.email(),
entity.emailVerified(),
entity.image(),
entity.createdAt(),
entity.updatedAt(),
entity.isActive()
)
}
}

View file

@ -0,0 +1,8 @@
package br.dev.jsilveira.coresync.user.adapter.out.persistence.repository
import br.dev.jsilveira.coresync.user.adapter.out.persistence.entity.UserEntity
import org.springframework.data.mongodb.repository.MongoRepository
interface UserRepository extends MongoRepository<UserEntity, UUID> {
}

View file

@ -3,7 +3,6 @@ package br.dev.jsilveira.coresync.user.domain.model
import br.dev.jsilveira.coresync.user.domain.exception.UserException
import java.time.LocalDateTime
import java.util.UUID
record User(
UUID id,

View file

@ -0,0 +1,42 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.mapper.WorkoutDayMapper
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository.WorkoutExerciseRepository
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository.WorkoutDayRepository
import br.dev.jsilveira.coresync.workout.application.port.out.LoadWorkoutDayPort
import br.dev.jsilveira.coresync.workout.application.port.out.SaveWorkoutDayPort
import br.dev.jsilveira.coresync.workout.domain.exception.WorkoutDayException
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutDay
import org.springframework.stereotype.Component
@Component
class WorkoutDayPersistenceAdapter implements SaveWorkoutDayPort, LoadWorkoutDayPort {
private final WorkoutDayRepository dayRepository
private final WorkoutExerciseRepository exerciseRepository
private final WorkoutDayMapper dayMapper = new WorkoutDayMapper()
WorkoutDayPersistenceAdapter(
WorkoutDayRepository dayRepository,
WorkoutExerciseRepository exerciseRepository
) {
this.dayRepository = dayRepository
this.exerciseRepository = exerciseRepository
}
@Override
void save(WorkoutDay workoutDay) {
def entity = dayMapper.toEntity(workoutDay)
dayRepository.save(entity)
}
@Override
WorkoutDay load(UUID id) {
def dayEntity = dayRepository.findById(id)
.orElseThrow({ new WorkoutDayException("Dia de treino não encontrado: ${id}") })
def exerciseEntities = exerciseRepository.findAllByWorkoutDayId(id)
return dayMapper.toDomain(dayEntity, exerciseEntities)
}
}

View file

@ -0,0 +1,34 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.mapper.WorkoutExerciseMapper
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository.WorkoutExerciseRepository
import br.dev.jsilveira.coresync.workout.application.port.out.LoadWorkoutExercisePort
import br.dev.jsilveira.coresync.workout.application.port.out.SaveWorkoutExercisePort
import br.dev.jsilveira.coresync.workout.domain.exception.WorkoutExerciseException
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutExercise
import org.springframework.stereotype.Component
@Component
class WorkoutExercisePersistenceAdapter implements SaveWorkoutExercisePort, LoadWorkoutExercisePort {
private final WorkoutExerciseRepository exerciseRepository
private final WorkoutExerciseMapper exerciseMapper = new WorkoutExerciseMapper()
WorkoutExercisePersistenceAdapter(WorkoutExerciseRepository exerciseRepository) {
this.exerciseRepository = exerciseRepository
}
@Override
void save(WorkoutExercise workoutExercise) {
def entity = exerciseMapper.toEntity(workoutExercise)
exerciseRepository.save(entity)
}
@Override
WorkoutExercise load(UUID id) {
def exerciseEntity = exerciseRepository.findById(id)
.orElseThrow({ new WorkoutExerciseException("Exercício não encontrado: ${id}") })
return exerciseMapper.toDomain(exerciseEntity)
}
}

View file

@ -1 +0,0 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence

View file

@ -0,0 +1,47 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.mapper.WorkoutPlanMapper
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository.WorkoutExerciseRepository
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository.WorkoutDayRepository
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository.WorkoutPlanRepository
import br.dev.jsilveira.coresync.workout.application.port.out.LoadWorkoutPlanPort
import br.dev.jsilveira.coresync.workout.application.port.out.SaveWorkoutPlanPort
import br.dev.jsilveira.coresync.workout.domain.exception.WorkoutPlanException
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutPlan
import org.springframework.stereotype.Component
@Component
class WorkoutPlanPersistenceAdapter implements SaveWorkoutPlanPort, LoadWorkoutPlanPort {
private final WorkoutPlanRepository planRepository
private final WorkoutDayRepository dayRepository
private final WorkoutExerciseRepository exerciseRepository
private final WorkoutPlanMapper planMapper = new WorkoutPlanMapper()
WorkoutPlanPersistenceAdapter(
WorkoutPlanRepository planRepository,
WorkoutDayRepository dayRepository,
WorkoutExerciseRepository exerciseRepository
) {
this.planRepository = planRepository
this.dayRepository = dayRepository
this.exerciseRepository = exerciseRepository
}
@Override
void save(WorkoutPlan workoutPlan) {
def entity = planMapper.toEntity(workoutPlan)
planRepository.save(entity)
}
@Override
WorkoutPlan load(UUID id) {
def planEntity = planRepository.findById(id)
.orElseThrow({ new WorkoutPlanException("Plano de treino não encontrado: ${id}") })
def dayEntities = dayRepository.findAllByWorkoutPlanId(id)
def exerciseEntities = exerciseRepository.findAllByWorkoutDayIdIn(dayEntities.collect { it.id() })
return planMapper.toDomain(planEntity, dayEntities, exerciseEntities)
}
}

View file

@ -0,0 +1,17 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity
import org.springframework.data.mongodb.core.mapping.Document
import java.time.LocalDateTime
@Document("workout_days")
record WorkoutDayEntity(
UUID id,
String name,
UUID workoutPlanId,
Boolean isRest,
String weekDay,
Integer estimatedDurationInSeconds,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {}

View file

@ -0,0 +1,18 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity
import org.springframework.data.mongodb.core.mapping.Document
import java.time.LocalDateTime
@Document("workout_exercises")
record WorkoutExerciseEntity(
UUID id,
Integer order,
String name,
Integer sets,
Integer reps,
Integer restTimeInSeconds,
UUID workoutDayId,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {}

View file

@ -0,0 +1,15 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity
import org.springframework.data.mongodb.core.mapping.Document
import java.time.LocalDateTime
@Document("workout_plans")
record WorkoutPlanEntity(
UUID id,
String name,
UUID userId,
Boolean isActive,
LocalDateTime createdAt,
LocalDateTime updateAt
) {}

View file

@ -0,0 +1,39 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.mapper
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutDayEntity
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutExerciseEntity
import br.dev.jsilveira.coresync.workout.domain.model.WeekDay
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutDay
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutExercise
class WorkoutDayMapper {
private final WorkoutExerciseMapper exerciseMapper = new WorkoutExerciseMapper()
WorkoutDayEntity toEntity(WorkoutDay domain) {
return new WorkoutDayEntity(
domain.id(),
domain.name(),
domain.workoutPlanId(),
domain.isRest(),
domain.weekDay() ? domain.weekDay().name() : null,
domain.estimatedDurationInSeconds(),
domain.createdAt(),
domain.updatedAt()
)
}
WorkoutDay toDomain(WorkoutDayEntity entity, List<WorkoutExerciseEntity> exerciseEntities) {
def exercises = exerciseEntities.collect { exerciseMapper.toDomain(it) }
return new WorkoutDay(
entity.id(),
entity.name(),
entity.workoutPlanId(),
entity.isRest(),
entity.weekDay() ? WeekDay.valueOf(entity.weekDay()) : null,
entity.estimatedDurationInSeconds(),
entity.createdAt(),
entity.updatedAt(),
exercises
)
}
}

View file

@ -0,0 +1,34 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.mapper
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutExerciseEntity
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutExercise
class WorkoutExerciseMapper {
WorkoutExerciseEntity toEntity(WorkoutExercise domain) {
return new WorkoutExerciseEntity(
domain.id(),
domain.order(),
domain.name(),
domain.sets(),
domain.reps(),
domain.restTimeInSeconds(),
domain.workoutDayId(),
domain.createdAt(),
domain.updatedAt()
)
}
WorkoutExercise toDomain(WorkoutExerciseEntity entity) {
return new WorkoutExercise(
entity.id(),
entity.order(),
entity.name(),
entity.sets(),
entity.reps(),
entity.restTimeInSeconds(),
entity.workoutDayId(),
entity.createdAt(),
entity.updatedAt()
)
}
}

View file

@ -0,0 +1,39 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.mapper
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutDayEntity
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutExerciseEntity
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutPlanEntity
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutDay
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutExercise
import br.dev.jsilveira.coresync.workout.domain.model.WorkoutPlan
class WorkoutPlanMapper {
private final WorkoutDayMapper dayMapper = new WorkoutDayMapper()
WorkoutPlanEntity toEntity(WorkoutPlan domain) {
return new WorkoutPlanEntity(
domain.id(),
domain.name(),
domain.userId(),
domain.isActive(),
domain.createdAt(),
domain.updateAt()
)
}
WorkoutPlan toDomain(WorkoutPlanEntity entity, List<WorkoutDayEntity> dayEntities, List<WorkoutExerciseEntity> exerciseEntities) {
def days = dayEntities.collect { dayEntity ->
def dayExercises = exerciseEntities.findAll { it.workoutDayId() == dayEntity.id() }
dayMapper.toDomain(dayEntity, dayExercises)
}
return new WorkoutPlan(
entity.id(),
entity.name(),
entity.userId(),
entity.isActive(),
entity.createdAt(),
entity.updateAt(),
days
)
}
}

View file

@ -0,0 +1,8 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutDayEntity
import org.springframework.data.mongodb.repository.MongoRepository
interface WorkoutDayRepository extends MongoRepository<WorkoutDayEntity, UUID> {
List<WorkoutDayEntity> findAllByWorkoutPlanId(UUID workoutPlanId)
}

View file

@ -0,0 +1,9 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutExerciseEntity
import org.springframework.data.mongodb.repository.MongoRepository
interface WorkoutExerciseRepository extends MongoRepository<WorkoutExerciseEntity, UUID> {
List<WorkoutExerciseEntity> findAllByWorkoutDayId(UUID workoutDayId)
List<WorkoutExerciseEntity> findAllByWorkoutDayIdIn(List<UUID> workoutDayIds)
}

View file

@ -0,0 +1,7 @@
package br.dev.jsilveira.coresync.workout.adapter.out.persistence.repository
import br.dev.jsilveira.coresync.workout.adapter.out.persistence.entity.WorkoutPlanEntity
import org.springframework.data.mongodb.repository.MongoRepository
interface WorkoutPlanRepository extends MongoRepository<WorkoutPlanEntity, UUID> {
}