4

I'm working on a networked game. I have tried to design the various components (client, server, engine) to only about things within their domain. For example, the server should only be concerned with handling packets and connections, not about entities within the game engine, or handling hardware events on the client-side. I haven't been able to find a nice way to stitch these domains together cleanly, because ultimately, an entity moving in the game world does eventually need to be represented to the user in some way. That's a related question, but perhaps not the core of this one.

With this in mind, I've been looking at some of my classes, and have wondered if they were designed in a clean way.

One example I have is a Party class, that represents a group of Pokemon (pets to battle with). My initial design looked something like

// Max number of Pokemon is 6.
public interface Party {

    Set<Pokemon> getPokemon();

    void add(Pokemon pokemon);

    void remove(Pokemon pokemon);

    boolean contains(Pokemon pokemon);

    boolean contains(Species species);

    int getNumberOfPokemon();
}

Most of the methods within the interface expose the Party's state. The client code was expected to check if the number of Pokemon within the party is less than 6, and if so, it was allowed to add to the party; as well as checking that there were at least 2 in the party before remove was called. I simplified it a little bit in an effort to expose less of the object's state, and was left with

public interface Party {

    boolean hasRoom();

    void add(Pokemon pokemon);

    void remove(Pokemon pokemon);
}

A Party is used in many places across the domains of the code. For example, there's a view on the client side that shows the user what is in their Party. It's also stored in a repository.

Should the Party class know how to send itself to the client, and repository?

public interface Party {

    // ...
    void saveTo(PartyRepository repo);

    void showUITo(Client client);
}

Sure, doing it that way keeps the state of the party locked within the object, but then you run the risk of having lots of almost-god objects, who know everything about how they're used.

One alternative I had thought of was to split the Party into two separate types, one specifically designed to represent the state, and one to represent behavior. But seeing as a Party is just a collection of Pokemon, it would really just be something like

public interface PartyDTO {

    Set<Pokemon> getMembers();
}

public interface PartyBehavior {

    void add(Pokemon pokemon);
}

That still doesn't feel right though because then which one is the Party? I'd still be manipulating the state of the PartyDTO class through the PartyBehavior class, so I don't feel like I'm really gaining anything.

Is this something that should be done? What alternatives are there?

  • You mention "across domains". Don't you mean "across components"? Your application likely has a single "problem domain" that represents your game "world" and the concepts in it, like Party and Pokemon. – aryeh Apr 12 '16 at 9:30
  • Yes, that is a better way to describe it i feel. – Zymus Apr 12 '16 at 15:08
1

What you're facing here is the tension between two issues: good encapsulation and the Single Responsibility Principle. If Party has to expose its inner state to everyone, it's poorly encapsulated with all the usual effects that has (in this case, that includes violating Tell, Don't Ask). But if it keeps its state encapsulated then only it has the access needed to save that state.

But this isn't always unsolvable. For example, for a party you can add to up to a limit, you could try a design like this:

public interface Party {
    Iterable<Pokemon> getMembers();
    Iterable<EmptySlot> getSlots(); // This could also be Optional<EmptySlot> getSlot();
}

public interface EmptySlot {
    void Fill(Pokemon member);
}

Now when you want to save your party, you can getMembers and pass that to the repository. When you want to add a member to the party, you getSlots, then pick one and fill it. There's no way to bypass this to create an invalid party, no need for exception handling, returning status codes/success flags or special validation methods.

  • I hadn't thought about that. That's actually a really nice way to do it. I suppose I should have waited a bit longer before assigning the bounty. I would still have to have some of the logic in the calling code (for example, to see if there are any slots before trying to fill one). – Zymus Apr 15 '16 at 16:36
  • This is a matter of personal preference, I guess. You would perhaps prefer this design over my proposed solution, I would choose mine. I think your design adds ambiguity, the remove and add methods are pretty self explanatory and also do not expose inner state. – Andy Apr 17 '16 at 13:45
  • @DavidPacker In a real project, I'd probably go for a less "fancy" option than this one at first, at least until I felt it was causing some kind of problem. – Ben Aaronson Apr 17 '16 at 20:50
4
+50

First of all, domain in domain-driven design (DDD further on) does not mean you divide your application into layers, which are only responsible for specific operations. Domain in DDD is the layer which contains your business rules and which is completely ignorat of anything else in the app. Be it persistence or presentation, the domain should not care how it is saved or displayed.

This directly contradicts the suggestion you made, that the Party should know how to save and display itself, which you made a case against right after (not having god objects is definitely a good approach).

Imagine you have a bowl representing a persistence layer (a database if you wish), which has apples (domain objects). Adding an apple to the bowl is equivalent to saving it to a database and removing an item from the bowl is equivalent to deleting it from the database.

What makes more sense?

  1. When you want to add an apple to the bowl, you bring the whole bowl containing millions of apples to the apple and then add the apple to the bowl.
  2. You bring the apple to the bowl and add it to it.

A lot of times, the programming approach is different from what you would see in the real world, here, it really is not. A repository works by having an item passed to it and the repository knows that to do with it.

On top of that, if you added the persisting logic directly to the domain class, you would now be either limited to only one way of saving the object (by destructuring the object into scalar data types and passing it into the repository), or delegated the complete object, this, to the repository, in which case it would not make sense to pass the repository in the first place, because you could just as well do something like repository.Save(PartyObject).

Most of the methods within the interface expose the Party's state. The client code was expected to check if the number of Pokemon within the party is less than 6, and if so, it was allowed to add to the party; as well as checking that there were at least 2 in the party before remove was called.

The initial design had flaws indeed, the promoted second version of the Party interface is better, as long as you actually need to ever extract the hasRoom value out of the object (for example for presentation layer).

If you do not, then even that method is necessary and the interface could be as simple as this:

public interface Party 
{
    // throws NoMoreRoomException when the party is full
    // throws PokemonAlreadyExistsException
    void add(Pokemon pokemon);

    // throws PokemonNotFoundException
    // throws MinimumNumberOfPokemonsReachedException when you try to remove
    // a pokemon from the party which already contains the minimum required
    // number of pokemons
    void remove(Pokemon pokemon);
}

An implementation of this interface will then state the rules. You could have a TwoToSixParty and SevenToTventyOneParty, both implementing the Party interface and throwing exceptions when either 7th or 22nd Pokemon was to be added to the collection or when a Pokemon was tried to be removed from the party already containing only two or seven pokemons respectively.

One alternative I had thought of was to split the Party into two separate types, one specifically designed to represent the state, and one to represent behavior.

This is perfectly valid design. The PartyDTO will be a stateless object, having getters and setters for all its properties and it will be the object providing middle layer between your domain and a concrete persisting method (be it saving a domain model - in which case a domain model is deconstructed to form the DTO, or using the DTO to construct the domain object).

The DTO does not need rules, because the domain itself ensures all the combinations of data that are passed to the DTOs are in valid state.

All in all, in the end you will need to access values of the domain models anyway, you need to print them out to the user, and for that you will need getters. But the getters should only be used for reading the values from the domain objects, you should never read an object value, evaluate its value and modify the object based on the value you just evaluated. That's logic of the class you pulled the information from and should be inside it.


Even then remember, not all "recommended" practices are actually good for all projects. If I were building a teeny tiny application I needed to deploy real quick, in a day or two, I probably would not mind the active record pattern, although I am usually strongly against it.

There's no good-for-everything approach in programming, almost everything has its pros and cons and you need to decide whether you want to spend time making 8 new interfaces just so you can abstract persisting an object, or if you just need to get it done.

  • +1 for the final 2 paragraphs. Keep things manageable and do what it takes to release v1.0. Learn as you go. You'll make both good and bad decisions, and you will be better at making such decisions in the future. – user44761 Apr 12 '16 at 18:27
2

"Should the Party class know how to send itself to the client, and repository?"

It is hard to answer your question because you are writing your application in a procedural rather than object-oriented style, despite the object-oriented and domain-driven-design tags. In an object-oriented program your Party object would constrain the number of Pokemons to 6, not client code. Your client would not ask the number in Party before the client decides to add one.

Nevertheless, let me answer assuming you were an object-oriented approach:-

"Should the Party class know how to send itself to the client, and repository?"

No. A Party object should only know and do things related to being a Party of Pokemons. It shouldn't know anything about persistence, human interaction or interaction with other systems. A PartyRepository on the other hand knows how to represent a Party in terms of interacting with a persistent store. For example, your PartyRepository might know how to save your Party.

If you want to save a Party, send a message (call a method) to PartyRepository with the Party to save.

  • upvote. Yes, make a class. An interface is the fundamental problem. The secondary problem is overthinking the problem; which is caused by the first problem. – radarbob Apr 15 '16 at 21:01
0

Should the Party class know how to send itself to the client, and repository?

I would suggest it shouldn't know about either of those.

Your Party object represents something in your domain model, so it shouldn't really need to know about networking.

Similarly if your repository is used to store Party objects then the Party shouldn't know anything about its owner/keeper, however your Repository could reasonably have an AddParty method.

One alternative I had thought of was to split the Party into two separate types, one specifically designed to represent the state, and one to represent behavior.

That still doesn't feel right though because then which one is the Party? I'd still be manipulating the state of the PartyDTO class through the PartyBehavior class, so I don't feel like I'm really gaining anything.

Instead of thinking in terms of "splitting behaviour and state" consider splitting Network serialisation away from domain objects instead.

DTOs are used for communicating data across a boundary, which is a problem you need to solve for your client/server communication; your Party domain object shouldn't need to know anything about networking or serialisation.

A DTO could be useful, but only for serialising data to your client, and not as a server-side "state" object. Creating a new PartyDTO wouldn't imply changing anything about your Party domain object or separating state and behaviour. Quote below from wikipedia's page on DTOs:

Data transfer object (DTO), formerly known as value objects or VO, is a design pattern used to transfer data between software application subsystems. DTOs are often used in conjunction with data access objects to retrieve data from a database.

The difference between data transfer objects and business objects or data access objects is that a DTO does not have any behaviour except for storage and retrieval of its own data (accessors and mutators).

-- https://en.wikipedia.org/wiki/Data_transfer_object

In your scenario, your server might create an instance of PartyDTO based on a Party every time it needs to publish an update to the client, but the DTO object would be short-lived for the network send/publish operation.

The advantage to writing a DTO is separation between your Party domain object on your server and network updates to clients. The Party class wouldn't care about network communication, and your client wouldn't need any knowledge of the real Party class, which would be hidden in the server.

The client code would be free to define its own client-specific code to handle showOnUI based on the content of PartyDTO in a clientside-only class.

Similarly, your Network client/server classes may need send/receive or publish/subscribe methods for PartyDTO - this could be completely decoupled from Party however.

e.g. in your ServerApp class, or whatever class glues your network code into your domain model:

public class ServerApp {
    public void SendUpdates() {
        foreach (Party p : partyRepository.getParties()) {
            PartyDTO dto = mapper.mapTo<PartyDTO>(p);
            networkServer.sendUpdate(dto);
        }
    }
}

The disadvantage is needing to write object-mapping code to generate the DTO from Party, which is tedious and very boiler-plate-ish if you're writing all of it by-hand, but you can take away most of that pain with a library like ModelMapper.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.