Overview
Addresses is an aggregate that manages all addresses for a specific party. It handles address definition, updates, and removal while enforcing policies and tracking events.
Package: com.softwarearchetypes.party
Source: /party/src/main/java/com/softwarearchetypes/party/Addresses.java:20
Key Features
- Enforces address defining policies
- Tracks events for all address operations
- Supports multiple address types (GeoAddress, EmailAddress, PhoneAddress, WebAddress)
- Handles idempotent operations (skips duplicate additions/updates)
- Version control for optimistic locking
Factory Methods
emptyAddressesFor
static Addresses emptyAddressesFor(PartyId partyId)
Creates an empty Addresses collection with default policy.
Empty Addresses with default policy
static Addresses emptyAddressesFor(PartyId partyId, AddressDefiningPolicy policy)
Creates an empty Addresses collection with custom policy.
policy
AddressDefiningPolicy
required
Custom address defining policy
Empty Addresses with custom policy
Example:
Addresses addresses = Addresses.emptyAddressesFor(partyId);
// With custom policy
Addresses restrictedAddresses = Addresses.emptyAddressesFor(
partyId,
AddressDefiningPolicy.maxOfType(AddressType.EMAIL, 1)
);
Accessor Methods
partyId
Returns the party identifier.
version
Returns the current version for optimistic locking.
events
List<AddressRelatedEvent> events()
Returns all address-related events.
return
List<AddressRelatedEvent>
Immutable list of events
asSet
Returns all addresses as a set.
publishedEvents
List<PublishedEvent> publishedEvents()
Returns only events marked for publishing.
Operations
addOrUpdate
Result<String, Addresses> addOrUpdate(Address address)
Adds a new address or updates an existing one. The operation:
- Checks if address already exists (by ID)
- If exists: updates with new data if different
- If new: checks policy and adds if allowed
- Records appropriate event
The address to add or update
return
Result<String, Addresses>
Success with Addresses, or failure with “POLICY_NOT_MET” or “NOT_MATCHING_ADDRESS_TYPE”
Example:
GeoAddress homeAddress = GeoAddress.builder()
.id(AddressId.random())
.partyId(partyId)
.street("123 Main St")
.city("Springfield")
.country("USA")
.useType(AddressUseType.HOME)
.build();
Result<String, Addresses> result = addresses.addOrUpdate(homeAddress);
if (result.success()) {
addresses = result.getSuccess();
// Check event
List<AddressRelatedEvent> events = addresses.events();
// Will contain GeoAddressDefined event
} else {
// Handle failure (policy not met)
System.out.println("Failed: " + result.getFailure());
}
Update behavior:
// First add
addresses.addOrUpdate(emailAddress1);
// Event: EmailAddressDefined
// Update with different data
EmailAddress updated = emailAddress1.withEmail("[email protected]");
addresses.addOrUpdate(updated);
// Event: EmailAddressUpdated
// Update with same data (idempotent)
addresses.addOrUpdate(updated);
// Event: AddressUpdateSkipped (no changes detected)
removeAddressWith
Result<String, Addresses> removeAddressWith(AddressId addressId)
Removes an address by ID. The operation is idempotent - removing a non-existent address succeeds but records a skip event.
The address identifier to remove
return
Result<String, Addresses>
Always succeeds with updated Addresses
Example:
Result<String, Addresses> result = addresses.removeAddressWith(addressId);
addresses = result.getSuccess();
List<AddressRelatedEvent> events = addresses.events();
// Last event is either:
// - EmailAddressRemoved (if existed)
// - AddressRemovalSkipped (if didn't exist)
Events
The Addresses aggregate generates the following events:
Definition Events
GeoAddressDefined: Geographic address added
EmailAddressDefined: Email address added
PhoneAddressDefined: Phone address added
WebAddressDefined: Web address added
Update Events
GeoAddressUpdated: Geographic address modified
EmailAddressUpdated: Email address modified
PhoneAddressUpdated: Phone address modified
WebAddressUpdated: Web address modified
AddressUpdateSkipped: Update had no changes
Removal Events
GeoAddressRemoved: Geographic address removed
EmailAddressRemoved: Email address removed
PhoneAddressRemoved: Phone address removed
WebAddressRemoved: Web address removed
AddressRemovalSkipped: Address didn’t exist
Address Types
The Addresses collection supports multiple address types:
GeoAddress
Physical geographic addresses with street, city, postal code, country.
GeoAddress address = GeoAddress.builder()
.partyId(partyId)
.street("123 Main St")
.city("Springfield")
.postalCode("12345")
.country("USA")
.useType(AddressUseType.HOME)
.build();
EmailAddress
Email addresses.
EmailAddress email = EmailAddress.builder()
.partyId(partyId)
.email("[email protected]")
.useType(AddressUseType.WORK)
.build();
PhoneAddress
Phone numbers with country code and extension.
PhoneAddress phone = PhoneAddress.builder()
.partyId(partyId)
.countryCode("+1")
.number("555-1234")
.useType(AddressUseType.MOBILE)
.build();
WebAddress
Web URLs.
WebAddress web = WebAddress.builder()
.partyId(partyId)
.url("https://example.com")
.useType(AddressUseType.WEBSITE)
.build();
Address Defining Policies
Policies control what addresses can be added:
Default Policy:
AddressDefiningPolicy.DEFAULT
// Allows all addresses
Custom Policies:
// Limit number of addresses per type
AddressDefiningPolicy.maxOfType(AddressType.EMAIL, 3);
// Require verification for certain types
AddressDefiningPolicy.requireVerified(AddressType.EMAIL);
// Combine policies
AddressDefiningPolicy.builder()
.maxOfType(AddressType.EMAIL, 3)
.maxOfType(AddressType.PHONE, 2)
.requireVerified(AddressType.EMAIL)
.build();
Complete Usage Example
// Create addresses for a party
Addresses addresses = Addresses.emptyAddressesFor(partyId);
// Add home address
GeoAddress home = GeoAddress.builder()
.id(AddressId.random())
.partyId(partyId)
.street("123 Main St")
.city("Springfield")
.postalCode("12345")
.country("USA")
.useType(AddressUseType.HOME)
.build();
Result<String, Addresses> result1 = addresses.addOrUpdate(home);
assert result1.success();
addresses = result1.getSuccess();
// Add email
EmailAddress email = EmailAddress.builder()
.id(AddressId.random())
.partyId(partyId)
.email("[email protected]")
.useType(AddressUseType.WORK)
.build();
Result<String, Addresses> result2 = addresses.addOrUpdate(email);
assert result2.success();
addresses = result2.getSuccess();
// Update email
EmailAddress updatedEmail = email.withEmail("[email protected]");
Result<String, Addresses> result3 = addresses.addOrUpdate(updatedEmail);
assert result3.success();
addresses = result3.getSuccess();
// Remove home address
Result<String, Addresses> result4 = addresses.removeAddressWith(home.id());
assert result4.success();
addresses = result4.getSuccess();
// Check final state
Set<Address> allAddresses = addresses.asSet();
assert allAddresses.size() == 1; // Only email remains
// Review events
List<AddressRelatedEvent> events = addresses.events();
// Events:
// 1. GeoAddressDefined
// 2. EmailAddressDefined
// 3. EmailAddressUpdated
// 4. GeoAddressRemoved
Version Control
Addresses uses version control for optimistic locking:
Addresses addresses = Addresses.emptyAddressesFor(partyId);
Version v1 = addresses.version(); // Version.initial()
addresses = addresses.addOrUpdate(address).getSuccess();
Version v2 = addresses.version(); // Version incremented
assert v2.isAfter(v1);
This prevents concurrent modification conflicts in multi-user scenarios.