I'm just trying to find some examples of Martin Fowler's Domain Model pattern and I can't.
From what I found on the Internet Domain Model is just adding some "logic" methods to classes. For example
public class Income {
private String title;
private String comment;
private String date;
private Double amount;
private Integer category;
public ExIn(String title, String comment, Double amount, Integer category, String date) {
this.title = title;
this.comment = comment;
this.date = date;
this.amount = amount;
this.category = category;
}
public Integer getCategory() {
return category;
}
public void setCategory(Integer category) {
this.category = category;
}
// more getters and setters
// Domain Model part starts
public boolean isPositive()
{
return amount > 0 ? true : false;
}
// more methods like that
}
Did I understand it correctly? If not, I'd be grateful for a little example of Domain Model Pattern usage.
Did I understand it correctly? If not, I'd be grateful for a little
example.
Broadly, yes.
From Martin Fowler, domain model is an object model of the domain that incorporates both behavior and data.
The domain model is frequently opposed to a model where you have specific classes to bear data and some other specific classes to bear behavior/processings.
If I take your Income class, it looks like more as a class that holds properties/data than an domain model with a real behavior.
public boolean isPositive(){
return amount > 0 ? true : false;
}
is a kind of utility function that has no relation with the model.
You could put that in a Math class.
I will try to give you a domain model example and then the version where the model separates data and processing.
Suppose in the requirements of the domain of the application you are modeling, we need to add a bonus for incomes. This bonus may take place in winter for Christmas for example (but why not for other events)
Rather than having a service class to do this processing, we let domain model objects perform the task.
Incomes, a high level object could iterate on Income instances and apply the bonus and we could have a bonus rule class that defines the bonus according to some input values.
I introduce multiple classes since the idea is to allow each objects to collaborate according to their responsibilities.
Incomes :
public class Incomes {
List<Income> incomes = ...
....
public void applyBonus(BonusRule bonusRule){
for (Income income : incomes){
income.applyBonus(bonusRule);
}
}
Income :
public class Income {
private float amount;
...
public void applyBonus(BonusRule bonusRule){
float bonus = bonusRule.compute(this);
amount += bonus;
}
...
}
ChristmasRule :
public class ChristmasBonusRule implements BonusRule {
...
#Override
public float compute(Income income){
float bonus = ...
return bonus;
}
...
}
And finally, we could apply the processing in this way :
void foo(){
// create a domain object that has both behavior and data
Incomes incomes = ...;
// invoke a functional method on the object by passing another domain object
incomes.applyBonus(new ChristmasBonusRule());
}
In a design where you separate data and logic in distinct classes, it could look like more like that :
public class IncomeBonusService {
// stateless : no incomes data inside it
....
public void applyChristmasBonus(List<Income> incomes){
for (Income income : incomes){
// Christmas bonus computation here
float bonus = ...
income.setAmount(bonus + income.getAmount());
}
}
}
And we could apply the processing in this way :
// inject the service
#Autowired
IncomeBonusService incomeBonusService;
void foo(){
// create a domain object that has only data
List<Income> incomes = ...;
// invoke a service method by passing data as parameter
incomeBonusService.applyChristmasBonus(incomes);
}
A model design where the objects have no behavior (only getter/setter) is called Anemic Domain Model.
Big differences between the two ways illustrated by this example :
Domain model :
The objects are meaningful.
Behavioral responsibility finely defined between classes.
So good isolation, testability and maintainability.
For example, adding/removing/unit-testing a BonusRule is easy.
Objects responsible of their state.
Indeed, no need to provide setters as the object can itself update its state after collaborating with other objects.
We could see that in Amount.applyBonus() :
float bonus = bonusRule.compute(this);
amount += bonus;
Anemic Domain Model :
All the logic is in the service class.
So a single place to get the code.
With few lines, it is fine.
But note that this advantage has a certain limit because as the logic becomes big or complex, the best thing is often splitting the logic in multiple service classes.
But whatever the number of Service classes you need, the whole logic is located in the service classes and not somewhere else. Which may ease development norms if we compare it to the domain model where the logic may be exploded in some different "types" of classes.
Necessity to provide getter/setter for domain classes.
The domain is not responsible of its state and its invariant rules either.
So any class that depends on the domain class can "break" its state.
As a side note, some frameworks (for persistence, mapping, serialization, ...) rely by default on getter/setter.
That's why this model, despite its drawbacks, leads in some projects.
Fowler's book that you cite refers to Larman's book for introductory understanding and examples.
Interestingly, Larman's approach to domain modeling doesn't ever add behavior to domain classes.
There is a notion in a domain model that a class is conceptual and is not a software class, but that software classes are based on domain (conceptual) classes. Larman's approach to implementing behavior follows responsibility driven design, and GoF design patterns.
The domain model remains a separate element in the software development process. The good aspect of this way of modeling is you separate the problem from the solution. A domain model is supposed to be true to the problem (capture requirements from the problem domain without addressing implementation details).
Larman presents "operation contracts" as a way to assure behaviors are consistent inside a domain model. Again, contracts are supposed to be independent of a solution (an implementation). Contracts have postconditions that describe a constraint in the domain model after an operation has taken place. An example of a postcondition would be that when a customer completes a purchase in a store, the sale object is associated with each of the items the customer purchased.
The implementation of the business logic should respect the contracts (postconditions) defined for the domain model. Larman's approach with the Controller GRASP pattern as well as other GRASP patterns ends up putting this logic in various classes (usually the domain layer, which is software classes inspired by conceptual classes in the domain model) or Façade (Controller) classes that handle the system operations.
Larman's approach is more complicated than this explanation, but the point is that behavior is never only defined alone in the domain model as methods. Larman says many times that domain (conceptual) classes do not have methods, as they are not software classes.
Fowler's book also refers to another book that he wrote on Analysis Patterns for examples.
The patterns come from various domains, including health care, financial trading, and accounting. Each of the patterns is described both textually and in a simple pre-UML notation (this book was written before the UML had stabilized into a usable form).
None of the examples in that book show software classes, that is with methods defined in a programming language (that I could find).
I know of at least one book where domain models in fields such as molecular biology have been published in UML. Here's an example (note the UML is modified -- sub-type boxes are shown in super-type boxes -- to save space):
The book above does not model behaviors, probably because they really depend on the requirements of the software application. These models capture some business rules, such as:
Each Chemical Formulation must be composed of 2 or more of either Chemical Elements or Chemical Compounds, or both.
But the models in that book are mostly data models.
Googling will find you this huge model for the Biomedical Research Integrated Domain Group (BRIDG). Drilling down to the Molecular Biology sub-domain, and to the class Gene, for example, you'll see it has no behavior, nor do any of the other (conceptual) classes in this domain model.
Are domain models in a programming language or not?
Larman's philosophy clearly shows them as programming language-independent (conceptual as opposed to software classes), as a separate artifact from code, to tie them explicitly to the problem domain (requirements).
On the other hand, you'll find Fowler saying, "I prefer POJO domain models.", which is pretty much saying domain models are defined in code.
Eric Evans' DDD makes the assumption that an important degree of complexity in much of software development comes from the domain, and so a model of such complex domains is essential to managing the complexity. Therefore, Domain Modeling is necessary when domains are complex. DDD suggests using a domain modeling language that is ubiquitous; that is, common to the domain experts and the developers. This would imply that in at least some cases, a domain model would not be defined in a programming language.
There is a related question that might shed some light (although it has generated a lot of heat). Some have criticized the question's example as being too trivial (not complex enough) for a justified domain model.
A "domain model" is simply an object which represents some discernible concept in your business domain. A "Customer", an "Order", etc. Whatever the business logic is, the tangible entities which make up that logic are the models in your domain.
Some will have lots of business logic (perhaps worth breaking up into other classes), some will have very little (or even none).
The difference between a "domain model" and any other class isn't a construct of the Java language itself, it's mainly a semantic construct of the business logic that you define.
Related
I have 2 dtos that are used differently, but they have similar fields. Eg, MyIdESDto uses info from MyIdDto. The latter object is built with data from the DB, while MyIdESDto is built from the ElasticSearch and completed with MyIdDto data.
In my opinion, there should be only 1 DTO, and the code set as many fields in the DTO as it wants. Other people say the MyIdESDto should extend MyIdDto. Other people agree with this implementation, saying that it won't break the code if some feature is deleted. Which one is better to use? Can you give me reasons of your choice?
#Data
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
public class MyIdESDto {
private String name;
private Integer id;
private Integer age;
public MyIdESDto(MyDto dto) {
this.name = dto.getName();
this.id = dto.getId();
this.aage = 42;
}
}
#Data
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
public class MyIdDto {
private String name;
private Integer id;
}
I would go with the current implementation, because of the same reason given by third group.
To me inheriting is not a good idea, because I don't see a is-a relationship here; they just have few similar, and somewhat related, properties.
Inheritance is a mean to express A is-a B. It is not meant as mean to prevent code duplication.
So, if you can find a meaningful explanation why MyIdEDto is also a MyIdDto - then inheritance is the natural fit. If not - then stay with two distinct classes.
On the other hand, sometimes pragmatism gives good guidance, too. So if the majority of your team finds the inheritance-solution to be "better" go for that.
In other words: this is really a "style" question. There is no "better", and no hard facts to go in either direction. Therefore: pick the solution that works best for the people responsible the code. Respectively for the people that are most affected by this decision (those two groups are not always identical).
DTOs are just sets of properties (i.e. no business logic). So the only duplication that you avoid by introducing a DTO hierachy, is duplication of setters and getters, which may result in several lines of code, but is not complex by itself.
If each DTO models a different concept, and both classes are not intended to be used in related code (where one could benefit from some OO abstraction), the the code setting as many fields as it wants makes difficult to follow which fields have been initialized at any time. (For instance, it is immediatly clear that MyIdDto does not have an age, but had we used MyIdESDto with age=0, we would have to trace where does the DTO come from, in order to know if we can trust in the value of MyIdESDto.age).
When faced with this issued, I usually think on the following way (which I agree, may be a bit controversial):
Duplicating code can, in some situations, keep software architectures
clean and understandable. Duplicates can also be used to keep
unreadable, complicated abstractions from entering the system(...)
Code that is abstracted to address two or more similar but separately
evolving requirements may be difficult to modify
“Cloning considered harmful” considered harmful:
patterns of cloning in software.
What is the difference between following two class in terms of data hiding(encapsulation).
In below example , I can access the value of member by making it public.
Eg: 1
public class App {
public int b = 10;
public static void main(String[] args) {
System.out.println(new App().b);
}
}
In below example, I can access the value of member by using getter method.
Eg : 2
class DataHiding
{
private int b;
public DataHiding() {
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
}
In both the above examples, I can access the value of member. Why Eg : 2, is called data hiding (encapsulation) ? If its not hiding the data.
Why Eg : 1 is not called encapsulated ?
What is it about
As you tagged this question with both java and object oriented programming oop, I suppose you are implicitly thinking about Java Beans. Nevertheless this is a question quite common across languages, take the wikipedia page on this matter :
In programming languages, encapsulation is used to refer to one of two
related but distinct notions, and sometimes to the combination1
thereof:
A language mechanism for restricting access to some of the object's
components.
A language construct that facilitates the bundling
of data with the methods (or other functions) operating on that
data.
Some programming language researchers and academics use the first
meaning alone or in combination with the second as a distinguishing
feature of object-oriented programming, while other programming
languages which provide lexical closures view encapsulation as a
feature of the language orthogonal to object orientation.
The second definition is motivated by the fact that in many OOP
languages hiding of components is not automatic or can be overridden;
thus, information hiding is defined as a separate notion by those who
prefer the second definition.
So encapsulation is not really about hiding data or information it about enclosing pieces of data in a language component (a class in Java). A Java Beans encapsulate data.
That being said, while encapsulation is one of the main feature of object oriented programming paradigm, at some point in the history of language design it was seen as not enough to help design better software.
History
One key practice to achieve better software design is decoupling, and encapsulation helps on that matter. Yet a cluster of data was not enough to help achieve this goal, other efforts in OOP pioneering were made in different language at that time, I believe SIMULA is the earliest language to introduce some kind of visibility keywords among other concepts like a class. Yet the idea of information hiding really appears later in 1972 with data that is only relevant to the component that uses it to achieve greater decoupling.
But back to the topic.
Answers to your questions
In this case data is encapsulated and public
This is commonly known as a global variable and it is usually regarded as a bad programming practice, because this may lead to coupling and other kind of bugs
Data is encapsulated and public (through method accessors)
This class is usually referred to as a Java Bean, these are an abomination if used in any other than what they were designed for.
These object were designed to fulfill a single role and that is quite specific is according to the specification
2.1 What is a Bean?
Let's start with an initial definition and then refine it:
“A Java Bean is a reusable software component that can be manipulated visually in a builder tool.”
Why is it an abomination nowadays ? Because people, framework vendors usually misuse them. The specification is not enough clear about that, yet there's some statement in this regard :
So for example it makes sense to provide the JDBC database access API as a class library rather than as a bean, because JDBC is essentially a programmatic API and not something that can be directly presented for visual manipulation.
I'd rather quote Joshua Bloch (more in this question and answer) :
"The JavaBeans pattern has serious disadvantages." - Joshua Bloch, Effective Java
Related points
As explained above one key practice to achieve better software is decoupling. Coupling has been one of the oldest battlefront of software engineers. Encapsulation, information hiding have a lot to do with the following practices to help decoupling for numerous reasons:
the Law of Demeter, breaking this law means the code has coupling. If one has to traverse a whole data graph by hand then, there's no information hiding, knowledge of the graph is outside of the component, which means the software is therefore less maintainable, less adaptable. In short : refactoring is a painful process. Anemic domain model suffer from that, and they are recognized as an anti-pattern.
A somehow modern practice that allows one to not break the Law of Demeter is Tell, Don't Ask.
That is, you should endeavor to tell objects what you want them to do; do not ask them questions about their state, make a decision, and then tell them what to do.
immutability, if data has to be public it should be immutable. In some degree if data is not needed, one module can introduce side effects in another ; if this was true for single threaded programs, it's even more painful with multi-threaded softwares. Today softwares and hardware are getting more and more multi-threaded, threads have to communicate, if an information has to be public it should be immutable. Immutability guarantee thread-safety, one less thing to worry about. Also immutability has to be guaranteed on the whole object graph.
class IsItImmutable {
// skipping method accessors for brevity
// OK <= String is immutable
private final String str;
// NOK <= java.util.Date is mutable, even if reference is final a date can be modified
private final Date date;
// NOK <= Set operations are still possible, so this set is mutable
private final Set<String> strs;
// NOK <= Set is immutable, set operations are not permitted, however Dates in the set are mutable
private final Set<Date> udates = Collections.unmodifiableSet(...);
// OK <= Set is immutable, set operations are not permitted, String is immutable
private final Set<String> ustrs = Collections.unmodifiableSet(...);
}
Using mutators and accessors hides the logic, not the name of the methods. It prevents users from directly modifying the class members.
In your second example, the user has no idea about the class member b, whereas in the first example, the user is directly exposed to that variable, having the ability to change it.
Imagine a situation where you want to do some validation before setting the value of b, or using a helper variable and methods that you don't want to expose. You'll encapsulate the logic in your setter and by doing that, you ensure that users cannot modify the variable without your supervision.
Encapsulation is not data hiding it is information hiding. You are hiding internal structure and data implementation, as well as data access logic.
For instance you can store your integer internally as String, if you like. In first case changing that internal implementation would mean that you have to also change all code that depends on b being an int. In second case accessor methods will protect internal structure and give you int, and you don't have to change the rest of the code if internals have changed.
Accessor methods also give you opportunity to restrict access to the data making it read-only or write-only in addition to plain read-write access. Not to mention other logic that can verify integrity of data going into the object as well as changing object state accordingly.
What happens if you want to retrieve the state of B without being able to change its value? you would create the getter but not the setter, you can't accomplish that by accessing B as a public int.
Also, in both methods get and set, if we had a more complex object, maybe we want to set or get some property or state of the object.
Example.
private MyObject a;
public setMyObjectName(String name){
MyObject.name = name;
}
public getMyObjectName(){
return MyObject.name;
}
This way we keep the object encapsulated, by restricting access to its state.
In java, all methods are virtual. This means, that if you extend some class, you can override the result of a method. Imagine for example the next class (continuing on your example):
class DataHidingDouble extends DataHiding{
public int getB(){
return b*2;
}
}
this means that you maintain control over what b is to the outer world in your subclass.
imagine also some subclass where the value of b comes from something that is not a variable, eg. a database. How are you then going to make b return the value if it is a variable.
It hides the data, not the value. Because the class is responsible for maintaining the data, and returning the correct value to the outside world.
I apologize in advance because this question is almost a bit silly. Nonetheless, I can't come up with a good solution myself, so I thought it still worthwhile to ask.
What does one do when the Representation object and the Domain object should have the same name? The DDD Sample doesn't really address the representation layer, so I was unable to find help there. An Account class works as well as any to illustrate:
package com.mycompany.myproduct.representation;
public class Account {
private String uuid;
private String accountNumber;
// etc
#JsonCreator
public Account(#JsonProperty('uuid) String ....
}
Maybe this system has a convention to return data as a String whenever possible. But I like having all the Json annotations here. While that means XML isn't really supported, that seems okay for now, though more flexibility there would be nice. Then in the Domain layer there might be another class like this:
package com.mycompany.myproduct.domain.model;
import java.uti.UUID;
public class Account extends Entity {
private UUID id;
private BigDecimal accountNumber;
// ... business logic, etc
}
If both of these datatypes are named the same the code will be ugly/unsupportable in the cases where they eventually have to meet. As part of the Ubiquitous Language, it seems like the Domain Layer HAS to have the Account Class. But the outside world talks in terms of the representation object. If that is their language, why should they use anything different?
Since these two classes are in separate namespaces, you could keep to the same name. But as you mentioned, this can get ugly and is certainly misleading.
I strongly advocate for pure, "real-world" naming in your domain layer. Account is a great example. Your rich Account domain entity, with its state, behaviour and invariants, represents an account in the real world.
Identifier naming outside of your domain should perhaps be more explicit to the context in which it is used. For example, I generally use the following:
AccountModel for an API response model.
AccountTable for a ORM class.
AccountDto for some transfer object (although please do try avoid DTOs!)
Everyone has their own standard. I believe the important thing to be consistency, especially when you are working with a team.
I have my code working, but I don't know if the way that I implemented it is appropriate. Basically, I want to maintain the pattern without violating it.
The code looks like this:
Package Model (with setters/getters omitted):
public class CA {
private Integer in;
private Integer jn;
}
public class CB {
private Integer kn;
private Integer ln;
}
public class CC {
private static CC instancia;
private CA a;
private CB b;
public static CC getInstancia() {
if(instancia == null) {
instancia = new CC();
}
return instancia;
}
}
Package Business:
class CCBusiness {
static CC c = CC.getInstancia();
void alter(Integer input) {
c.getCA.setIn(input);
Integer num = c.getCB.getLn();
}
}
Package Facade:
class FacadeOne {
void methodOne() {
CCBusiness.alter(1);
// And more xxBusiness.xx()
}
The real code is more complex, but to explain my doubts, I think this should work.
In one facade I call several Business objects, but it is appropriate that one Business (in this case, the one of CC class) can modify attributes from other classes (in this case, the ones inside CC)? Should I create CABusiness and CBBusiness?
Because, what I understand, one Business can't call another Business, so the second as to be parametrized to receive the object from FacadeOne (if I create CABusiness and CBBusiness)?
I think some clarifications might help you: The facade pattern helps you to have a single point of access for several classes which are hidden behind the facade and thus hidden to the outside world. Usually those classes form some kind of module or logical unit.
What you are struggling with is the structure behind the facade and their hierarchy. This is hard to analyse without knowing the whole picture, but from the information I have it would be best to have several you your Business classes, which can be individually called from the facade. Creating cross-callings between the Business objects will bear the chance to spaghettify your code.
As for best practices and techniques, the simplest one is to draw a sketch of your classes, which usually clarifies a lot. And you're already half way to UML based documentation. :-)
By the way, avoid giving your classes names like CA, CB... It's the same like naming variables a001, a002... Speaking names do a lot for readability!
By having a Facade you can get away with calling multiple CxBusiness objects and integrating their operations into a meaningful result. That is the purpose of a Facade, to simplify the interaction with the Business layer by hiding away interactions of 5 different components behind a concise and clear operation: methodOne.
For the individual CxBusiness however, you want to avoid cross-calling among each other; otherwise, you will end up with a complex dependency structure that could potentially run into circular references. Keep each CxBusiness as the sole wrapper for each Cx model and you will reduce the number of unwanted side-effects when interacting with them. Any interactions among these will take place in the facade.
Furthermore, enforce this pattern by having the facade depend upon interfaces rather than concrete classes: ICABusiness, ICCBusiness, etc. Then, the only way to access any model should be through these interfaces, and obviously, you should not have a concrete CxBusiness with a ICxBusiness member (no cross-dependencies). Once you put these restrictions in place, the implementation itself will flow towards a more modular and less coupled design.
In Java, I would like to use hierarchies of immutable POJOs to express my domain model.
e.g.
final ServiceId id = new ServiceId(ServiceType.Foo, "my-foo-service")
final ServiceConfig cfg = new ServiceConfig("localhost", 8080, "abc", JvmConfig.DEFAULT)
final ServiceInfo info = new ServiceInfo(id, cfg)
All of these POJOs have public final fields with no getters or setters. (If you are a fan of getters, please pretend that the fields are private with getters.)
I would also like to serialize these objects using the MessagePack library in order to pass them around over the network, store them to ZooKeeper nodes, etc.
The problem is that MessagePack only supports serialization of public, non-final fields, so I cannot serialize the business objects as-is. Also MessagePack does not support enum, so I have to convert enum values to int or String for serialization. (Yes it does, if you add an annotation to your enums. See my comment below.)
To deal with this I have a hand-written corresponding hierarchy of "message" objects, with conversions between each business object and its corresponding message object. Obviously this is not ideal because it causes a large amount of duplicated code, and human error could result in missing fields, etc.
Are there any better solutions to this problem?
Code generation at compile time?
Some way to generate the appropriate serializable classes at runtime?
Give up on MessagePack?
Give up on immutability and enums in my business objects?
Is there some kind of generic wrapper library that can wrap a mutable object (the message object) into an immutable one (the business object)?
MessagePack also supports serialization of Java Beans (using the #MessagePackBeans annotation), so if I can automatically convert an immutable object to/from a Java Bean, that may get me closer to a solution.
Coincidentally, I recently created a project that does pretty much exactly what you are describing. The use of
immutable data models provides huge benefits, but many serialization technologies seem to approach
immutability as an afterthought. I wanted something that would fix this.
My project, Grains, uses code generation to create an immutable implementation
of a domain model. The implementation is generic enough that it can be adapted to different serialization frameworks.
MessagePack, Jackson, Kryo, and standard Java serialization are supported so far.
Just write a set of interfaces that describe your domain model. For example:
public interface ServiceId {
enum ServiceType {Foo, Bar}
String getName();
ServiceType getType();
}
public interface ServiceConfig {
enum JvmConfig {DEFAULT, SPECIAL}
String getHost();
int getPort();
String getUser();
JvmConfig getType();
}
public interface ServiceInfo {
ServiceId getId();
ServiceConfig getConfig();
}
The Grains Maven plugin then generates immutable implementations of these interfaces at compile time.
(The source it generates is designed to be read by humans.) You then create instances of your objects. This example
shows two construction patterns:
ServiceIdGrain id = ServiceIdFactory.defaultValue()
.withType(ServiceType.Foo)
.withName("my-foo-service");
ServiceConfigBuilder cfg = ServiceConfigFactory.newBuilder()
.setHost("localhost")
.setPort(8080)
.setUser("abc")
.setType(JvmConfig.DEFAULT);
ServiceInfoGrain info = ServiceInfoFactory.defaultValue()
.withId(id)
.withConfig(cfg.build());
Not as simple as your public final fields, I know, but inheritance and composition are not possible without getters
and setters. And, these objects are easily read and written with MessagePack:
MessagePack msgpack = MessagePackTools.newGrainsMessagePack();
byte[] data = msgpack.write(info);
ServiceInfoGrain unpacked = msgpack.read(data, ServiceInfoGrain.class);
If the Grains framework doesn't work for you, feel free to inspect its MessagePack templates.
You can write a generic TemplateBuilder that uses reflection to set the final fields of your hand-written domain model. The trick
is to create a custom TemplateRegistry that allows registration of your custom builder.
It sounds like you have merged, rather than separated, the read and write concerns of your application. You should probably consider CQRS at this point.
In my experience, immutable domain objects are almost always attached to an audit story (requirement), or it's lookup data (enums).
Your domain should probably be, mostly, mutable, but you still don't need getters and setters. Instead you should have verbs on your objects which result in a modified domain model, and which raise events when something interesting happens in the domain (interesting to the business -- business == someone paying for your time). It's probably the events that you're interested in passing over the wire, not the domain objects. Maybe it's even the commands (these are similar to events, but the source is an agent external to the bounded context in which your domain lives -- events are internal to the model's bounded context).
You can have a service to persist the events (and another one to persist commands), which is also your audit-log (fulfilling your audit stories).
You can have an event handler that pushes your events onto your bus. These events should contain either simple information or entity ID's. The services that respond to these events should perform their duties using the information provided, or they should query for the information they need using the given ID's.
You really shouldn't be exposing the internal state of your domain model. You're breaking encapsulation by doing that, and that's not really a desirable thing to do. If I were you I'd take a look at the Axon Framework. It's likely to get you further than MessagePack alone.