refactor: Migrate from monolithic to modular architecture

### **Service-Implementation**
- [ ] **Tag 1**: Members-Service REST-API implementieren
- [ ] **Tag 2**: Database-Migrations und Repository-Layer
- [ ] **Tag 3**: Event-Publishing nach Kafka aktivieren
- [ ] **Tag 4**: Horses-Service analog implementieren
- [ ] **Tag 5**: Integration-Tests für beide Services
- [ ] **Tag 6-7**: Events-Service und Masterdata-Service
This commit is contained in:
stefan
2025-07-24 17:18:22 +02:00
parent dbbc303068
commit a4c7d53aa3
27 changed files with 2582 additions and 29 deletions
@@ -0,0 +1,82 @@
package at.mocode.members.domain.events
import com.benasher44.uuid.Uuid
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
/**
* Base interface for all member domain events.
*/
sealed interface MemberEvent {
val eventId: String
val memberId: Uuid
val timestamp: Instant
val eventType: String
}
/**
* Event published when a new member is created.
*/
data class MemberCreatedEvent(
override val eventId: String,
override val memberId: Uuid,
override val timestamp: Instant,
val firstName: String,
val lastName: String,
val email: String,
val membershipNumber: String,
val membershipStartDate: LocalDate,
val isActive: Boolean
) : MemberEvent {
override val eventType: String = "MemberCreated"
}
/**
* Event published when a member is updated.
*/
data class MemberUpdatedEvent(
override val eventId: String,
override val memberId: Uuid,
override val timestamp: Instant,
val firstName: String,
val lastName: String,
val email: String,
val membershipNumber: String,
val membershipStartDate: LocalDate,
val membershipEndDate: LocalDate?,
val isActive: Boolean,
val changes: Map<String, Any?>
) : MemberEvent {
override val eventType: String = "MemberUpdated"
}
/**
* Event published when a member is deleted.
*/
data class MemberDeletedEvent(
override val eventId: String,
override val memberId: Uuid,
override val timestamp: Instant,
val membershipNumber: String,
val firstName: String,
val lastName: String
) : MemberEvent {
override val eventType: String = "MemberDeleted"
}
/**
* Event published when a member's membership is about to expire.
*/
data class MembershipExpiringEvent(
override val eventId: String,
override val memberId: Uuid,
override val timestamp: Instant,
val membershipNumber: String,
val firstName: String,
val lastName: String,
val email: String,
val membershipEndDate: LocalDate,
val daysUntilExpiry: Int
) : MemberEvent {
override val eventType: String = "MembershipExpiring"
}
@@ -0,0 +1,127 @@
package at.mocode.members.domain.model
import at.mocode.core.domain.serialization.KotlinInstantSerializer
import at.mocode.core.domain.serialization.KotlinLocalDateSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuid4
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable
/**
* Domain model representing a member in the member management system.
*
* This entity represents a member of the organization with their personal
* information and membership details.
*
* @property memberId Unique internal identifier for this member (UUID).
* @property firstName First name of the member.
* @property lastName Last name of the member.
* @property email Email address of the member.
* @property phone Phone number of the member (optional).
* @property dateOfBirth Date of birth of the member (optional).
* @property membershipNumber Unique membership number.
* @property membershipStartDate Date when membership started.
* @property membershipEndDate Date when membership ends (optional).
* @property isActive Whether the membership is currently active.
* @property address Address of the member (optional).
* @property emergencyContact Emergency contact information (optional).
* @property createdAt Timestamp when this record was created.
* @property updatedAt Timestamp when this record was last updated.
*/
@Serializable
data class Member(
@Serializable(with = UuidSerializer::class)
val memberId: Uuid = uuid4(),
// Personal Information
var firstName: String,
var lastName: String,
var email: String,
var phone: String? = null,
@Serializable(with = KotlinLocalDateSerializer::class)
var dateOfBirth: LocalDate? = null,
// Membership Information
var membershipNumber: String,
@Serializable(with = KotlinLocalDateSerializer::class)
var membershipStartDate: LocalDate,
@Serializable(with = KotlinLocalDateSerializer::class)
var membershipEndDate: LocalDate? = null,
var isActive: Boolean = true,
// Additional Information
var address: String? = null,
var emergencyContact: String? = null,
// Audit Fields
@Serializable(with = KotlinInstantSerializer::class)
val createdAt: Instant = Clock.System.now(),
@Serializable(with = KotlinInstantSerializer::class)
var updatedAt: Instant = Clock.System.now()
) {
/**
* Returns the full name of the member.
*/
fun getFullName(): String {
return "$firstName $lastName"
}
/**
* Checks if the membership is currently valid.
*/
fun isMembershipValid(): Boolean {
// Simplified implementation - can be enhanced with proper date comparison
return isActive && membershipEndDate != null
}
/**
* Validates that the member data is consistent.
*/
fun validate(): List<String> {
val errors = mutableListOf<String>()
if (firstName.isBlank()) {
errors.add("First name is required")
}
if (lastName.isBlank()) {
errors.add("Last name is required")
}
if (email.isBlank()) {
errors.add("Email is required")
} else if (!isValidEmail(email)) {
errors.add("Email format is invalid")
}
if (membershipNumber.isBlank()) {
errors.add("Membership number is required")
}
membershipEndDate?.let { endDate ->
if (endDate < membershipStartDate) {
errors.add("Membership end date cannot be before start date")
}
}
return errors
}
/**
* Creates a copy of this member with updated timestamp.
*/
fun withUpdatedTimestamp(): Member {
return this.copy(updatedAt = Clock.System.now())
}
private fun isValidEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
}
@@ -0,0 +1,139 @@
package at.mocode.members.domain.repository
import at.mocode.members.domain.model.Member
import com.benasher44.uuid.Uuid
import kotlinx.datetime.LocalDate
/**
* Repository interface for Member entities.
*
* This interface defines the contract for data access operations
* related to members in the member management bounded context.
*/
interface MemberRepository {
/**
* Finds a member by their unique identifier.
*
* @param id The unique identifier of the member
* @return The member if found, null otherwise
*/
suspend fun findById(id: Uuid): Member?
/**
* Finds a member by their membership number.
*
* @param membershipNumber The membership number to search for
* @return The member if found, null otherwise
*/
suspend fun findByMembershipNumber(membershipNumber: String): Member?
/**
* Finds a member by their email address.
*
* @param email The email address to search for
* @return The member if found, null otherwise
*/
suspend fun findByEmail(email: String): Member?
/**
* Finds members by name (partial match on first or last name).
*
* @param searchTerm The search term to match against member names
* @param limit Maximum number of results to return
* @return List of matching members
*/
suspend fun findByName(searchTerm: String, limit: Int = 50): List<Member>
/**
* Finds all active members.
*
* @param limit Maximum number of results to return
* @param offset Number of results to skip
* @return List of active members
*/
suspend fun findAllActive(limit: Int = 100, offset: Int = 0): List<Member>
/**
* Finds all members (active and inactive).
*
* @param limit Maximum number of results to return
* @param offset Number of results to skip
* @return List of all members
*/
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<Member>
/**
* Finds members whose membership started within a date range.
*
* @param startDate The earliest membership start date to include
* @param endDate The latest membership start date to include
* @return List of members within the specified date range
*/
suspend fun findByMembershipStartDateRange(startDate: LocalDate, endDate: LocalDate): List<Member>
/**
* Finds members whose membership expires within a date range.
*
* @param startDate The earliest membership end date to include
* @param endDate The latest membership end date to include
* @return List of members whose membership expires within the specified date range
*/
suspend fun findByMembershipEndDateRange(startDate: LocalDate, endDate: LocalDate): List<Member>
/**
* Finds members with expiring memberships (within the next specified days).
*
* @param daysAhead Number of days to look ahead for expiring memberships
* @return List of members with expiring memberships
*/
suspend fun findMembersWithExpiringMembership(daysAhead: Int = 30): List<Member>
/**
* Saves a member (insert or update).
*
* @param member The member to save
* @return The saved member
*/
suspend fun save(member: Member): Member
/**
* Deletes a member by their ID.
*
* @param id The unique identifier of the member to delete
* @return True if the member was deleted, false if not found
*/
suspend fun delete(id: Uuid): Boolean
/**
* Counts the number of active members.
*
* @return The number of active members
*/
suspend fun countActive(): Long
/**
* Counts the total number of members.
*
* @return The total number of members
*/
suspend fun countAll(): Long
/**
* Checks if a membership number already exists.
*
* @param membershipNumber The membership number to check
* @param excludeMemberId Optional member ID to exclude from the check (for updates)
* @return True if the membership number exists, false otherwise
*/
suspend fun existsByMembershipNumber(membershipNumber: String, excludeMemberId: Uuid? = null): Boolean
/**
* Checks if an email address already exists.
*
* @param email The email address to check
* @param excludeMemberId Optional member ID to exclude from the check (for updates)
* @return True if the email exists, false otherwise
*/
suspend fun existsByEmail(email: String, excludeMemberId: Uuid? = null): Boolean
}