Sample domain service

Let us create a sample domain service based on our table reservation system. As discussed in this chapter, the importance of an efficient domain layer is the key to successful products or services. Projects developed based on the domain layer are more maintainable, highly cohesive, and decoupled. They provide high scalability in terms of business requirement change and have a low impact on the design of other layers.

Domain-driven development is based on domain, hence it is not recommended that you use a top-down approach where the UI would be developed first, followed by the rest of the layers and finally the persistence layer, or a bottom-up approach where the persistence layer like the DB is designed first and then the rest of the layers, with the UI at last.

Having a domain model developed first, using the patterns described in this book, gives clarity to all team members functionality wise and an advantage to the software designer to build a flexible, maintainable and consistent system that helps the organization to launch a world class product with less maintenance costs.

Here, you will create a restaurant service that provides the feature to add and retrieve restaurants. Based on implementation, you can add other functionalities such as finding restaurants based on cuisine or on rating.

Start with the entity. Here, the restaurant is our entity as each restaurant is unique and has an identifier. You can use an interface or set of interfaces to implement the entity in our table reservation system. Ideally, if you go by the interface segregation principle, you will use a set of interfaces rather than a single interface.

Note

The Interface Segregation Principle (ISP): clients should not be forced to depend upon interfaces that they do not use.

Entity implementation

For the first interface you could have an abstract class or interface that is required by all the entities. For example if we consider ID and name, attributes would be common for all entities. Therefore, you could use the abstract class Entity as an abstraction of entity in your domain layer:

public abstract class Entity<T> {

    T id;
    String name;

}

Based on that you can also have another abstract class that inherits Entity, an abstract class:

public abstract class BaseEntity<T> extends Entity<T> {

    private T id;
    public BaseEntity(T id, String name) {
        super.id = id;
        super.name = name;

    }
    ... (getter/setter and other relevant code)
}

Based on the preceding abstractions, we could create the Restaurant entity for restaurant management.

Now since we are developing the table reservation system, Table is another important entity in terms of the domain model. So, if we go by the aggregate pattern, restaurant would work as a root, and table would be internal to the Restaurant entity. Therefore, the Table entity would always be accessible using the Restaurant entity.

You can create the Table entity using the following implementation, and you can add attributes as you wish. For demonstration purpose only, basic attributes are used:

public class Table extends BaseEntity<BigInteger> {

    private int capacity;

    public Table(String name, BigInteger id, int capacity) {
        super(id, name);
        this.capacity = capacity;
    }

    public void setCapacity(int capacity) {
        this.capacity = capacity;
    }

    public int getCapacity() {
        return capacity;
    }
}

Now, we can implement the aggregator Restaurant shown as follows. Here, only basic attributes are used. You could add as many you want or may add other features also:

public class Restaurant extends BaseEntity<String> {

    private List<Table> tables = new ArrayList<>();
    public Restaurant(String name, String id, List<Table> tables) {
        super(id, name);
        this.tables = tables;
    }

    public void setTables(List<Table> tables) {
        this.tables = tables;
    }

    public List<Table> getTables() {
        return tables;
    }
}

Repository implementation

Now, we can implement the repository pattern as learned in this chapter. To start with, you will first create the two interfaces Repository and ReadOnlyRepository. ReadOnlyRepository will be used to provide abstraction for read only operations whereas Repository abstraction will be used to perform all types of operations:

public interface ReadOnlyRepository<TE, T> {

    boolean contains(T id);

    Entity get(T id);

    Collection<TE> getAll();
}

Based on this interface, we could create the abstraction of the repository that would do additional operations such as adding, removing, and updating:

public interface Repository<TE, T> extends ReadOnlyRepository<TE, T> {

    void add(TE entity);

    void remove(T id);

    void update(TE entity);
}

Repository abstraction as defined previously could be implemented in a way that suits you to persist your objects. The change in persistence code, that is a part of infrastructure layer, won't impact on your domain layer code as the contract and abstraction are defined by the domain layer. The domain layer uses the abstraction classes and interfaces that remove the use of direct concrete class and provides the loose coupling. For demonstration purpose, we could simple use the map that remains in the memory to persist the objects:

public interface RestaurantRepository<Restaurant, String> extends Repository<Restaurant, String> {

    boolean ContainsName(String name);
}

public class InMemRestaurantRepository implements RestaurantRepository<Restaurant, String> {

    private Map<String, Restaurant> entities;

    public InMemRestaurantRepository() {
        entities = new HashMap();
    }

    @Override
    public boolean ContainsName(String name) {
        return entities.containsKey(name);
    }

    @Override
    public void add(Restaurant entity) {
        entities.put(entity.getName(), entity);
    }

    @Override
    public void remove(String id) {
        if (entities.containsKey(id)) {
            entities.remove(id);
        }
    }

    @Override
    public void update(Restaurant entity) {
        if (entities.containsKey(entity.getName())) {
            entities.put(entity.getName(), entity);
        }
    }

    @Override
    public boolean contains(String id) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public Entity get(String id) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public Collection<Restaurant> getAll() {
        return entities.values();
    }

}

Service implementation

In the same way as the preceding approach, you could divide the abstraction of domain service into two parts: main service abstraction and read only service abstraction:

public abstract class ReadOnlyBaseService<TE, T> {

    private Repository<TE, T> repository;

    ReadOnlyBaseService(Repository<TE, T> repository) {
        this.repository = repository;
    }
    ...
}

Now, we could use this ReadOnlyBaseService to create the BaseService. Here, we are using the dependency inject pattern via a constructor to map the concrete objects with abstraction:

public abstract class BaseService<TE, T> extends ReadOnlyBaseService<TE, T> {
    private Repository<TE, T> _repository;

    BaseService(Repository<TE, T> repository) {
        super(repository);
        _repository = repository;
    }

    public void add(TE entity) throws Exception {
        _repository.add(entity);
    }

    public Collection<TE> getAll() {
        return _repository.getAll();
    }
}

Now, after defining the service abstraction services, we could implement the RestaurantService in the following way:

public class RestaurantService extends BaseService<Restaurant, BigInteger> {

    private RestaurantRepository<Restaurant, String> restaurantRepository;

    public RestaurantService(RestaurantRepository repository) {
        super(repository);
        restaurantRepository = repository;
    }

    public void add(Restaurant restaurant) throws Exception {
        if (restaurantRepository.ContainsName(restaurant.getName())) {
            throw new Exception(String.format("There is already a product with the name - %s", restaurant.getName()));
        }

        if (restaurant.getName() == null || "".equals(restaurant.getName())) {
            throw new Exception("Restaurant name cannot be null or empty string.");
        }
        super.add(restaurant);
    }
}

Similarly, you could write the implementation for other entities. This code is a basic implementation and you might add various implementations and behaviors in the production code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset