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:
+82
@@ -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(".")
|
||||
}
|
||||
}
|
||||
+139
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user