While following DDD concept I'm struggling on decision if I should make my domain localization aware? I came with two two solutions how to solve this. Both makes domain localization aware in different places. Should I even place localized text to domain? Share your solution for this problem or pros and cons of my two examples. Thanks.
Example 1
class Persion {
String name;
// other fields ommited
void rename(String newName) {
String oldName = this.name;
this.name = newName
// publish event with old name and new name
}
String name() {
return name;
}
}
class PersionRepository {
void store(Persion persion) {
Locale loc = LocaleContextHolder.get().getLocale();
// store object to DAO - create/update fields for context locale
}
// other methods ommited
}
Example 2
class Persion {
Map<Locale, String> name;
// other fields ommited
void rename(String newName) {
Locale locale = LocaleContextHolder.get().getLocale();
String oldName = this.name.put(locale, newName);
// publish event with old name and new name
}
String name() {
Locale locale = LocaleContextHolder.get().getLocale();
return this.name.get(locale);
}
}
class PersionRepository {
void store(Persion persion) {
// store object to DAO - create/update fields for all locales
}
// other methods ommited
}
In most of cases, the best option is to remove localization from the domain.
Domain classes should only contain data that are relevant to their invariants, since they are responsible for business rules. To retrieve localized descriptions, use projective DTOs and applicative services.
You could use something like this:
public final class VatCode {
private final String _code;
public VatCode(String code)
{
// VAT code validation here...
_code = code;
}
@Override
public String toString() {
return _code;
}
@Override
public boolean equals(Object obj) {
// ...
}
@Override
public int hashCode() {
// ...
}
}
public class Person {
private final VatCode _identifier;
public Person(VatCode identifier)
{
_identifier = identifier;
}
// some command and some query here...
}
public class PersonDTO {
private final String _vatCode;
private final String _personalDescription;
public PersonDTO(String _vatCode, String _personalDescription) {
this._vatCode = _vatCode;
this._personalDescription = _personalDescription;
}
// other fields here...
public String getVatCode()
{
return _vatCode;
}
public String getPersonalDescription()
{
return _personalDescription;
}
// some more getter here
}
public interface LocalizedPersonalInformationService {
PersonDTO getInformationOf(VatCode person, Locale localization) throws ProperExceptionList;
}
That is:
- something like a
VatCode
valueobject (that overrides equals, hashCode and toString) to identify the Person
entity
- a
Person
entity, holding the minimum amount of data required to ensure business invariants and exposing a set of command and queries
- a
PersonDTO
that carries useful descriptions (some call this a read-model)
- a
LocalizedPersonalInformationService
that is able to provide PersonDTO
s.
- and (obviously) all the needed exceptions... :-)
If at all possible put all your localization in the UI layer. Sometimes people find that difficult to do. For example, I worked on a project where the business logic would throw an exception and that exception would get displayed in the UI. To localize the exception we had to do something like the following (details omitted for brevity, also we had to have a LocalizedRuntimeException
and a LocalizedException
):
//====ArbitraryBusinessLogic.java====
if(badThing) {
throw new SubclassOfLocalizedException(LocalizedStrings.ERROR_FOO,param1,param2);
}
//====LocalizedException.java====
public class LocalizedException extends Exception {
private LocalizationKey localizationKey;
Object [] params;
public LocalizedException(LocalizationKey localizationKey, Object ... params) {
super();
localizationKey = localizationKey
params = params;
}
public String getLocalizedMessage(Locale locale) {
//message would be something like "The %s foo'd the %s"
//or of course "le %s foo'd le %s" (FYI: I don't speak French)
String message = getLocalizedMessageForKey(localizationKey);
return String.format(locale,message,params);
}
public String getLocalizedMessage() {
return getLocalizedMessage(getDefaultLocale());
}
public String getMessage() {
return getLocalizedMessage();
}
}