윈도우 앱개발을 향하여

블로그 이미지
윈도우 10 스토어에 앱을 개발해 올리는 것을 목표로 하고 있습니다. 비전공자가 독학으로 시도하는 일이어서 얼마나 걸릴지 모르겠지만... 아무튼 목표는 그렇습니다!!
by 코딩하는 경제학도
  • Total hit
  • Today hit
  • Yesterday hit

Copyright

이 모든 내용은 Pluralsight에 Dino Esposito가 올린 'Modern Software Architecture: Domain Models, CQRS, and Event Sourcing'라는 강의의 네번째 챕터를 듣고 정리한 것입니다(https://app.pluralsight.com/library/courses/modern-software-architecture-domain-models-cqrs-event-sourcing/table-of-contents). 강의 원작자분께 게시허가도 받았습니다.


Content

Discovering the Domain Architecture through DDD

The DDD Layered Architecture

The "Domain Model" Supporting Architecture

The CQRS Supporting Architecture

Event Sourcing

Designing Software Driven by the Domain



Domain Layer

One of the most common supporting architectures for bounded context is the layered architecture in which the domain model pattern is used to organize and express the business logic.


The domain layer is made of two main components, the domain model and domain services


Domain Model (assume that the model is an object model)

In the context of DDD, a domain model is simply a software model that fully represents the business domain. Most of the time a domain model is an object-oriented model, and if so, it is characterized by classes with very specific roles and very specific features, aggregates, entities, values types, and factories


Abstractly speaking, the domain model is made of classes that represent entities and values. Concretely, when you get to write such a software artifact, you identify one, or even likely more, modules. In DDD, a model is just the same as a .NET namespace that you use to organize classes in a class library project.


Aspects of a Domain Model Module

A domain-driven design module contains value objects, as well as entities. In the end, both are rendered as .NET classes, but entities and value objects represent quite different concepts and lead to different implementation details.


A value object is fully described by its attributes. The attributes of a value object never change once the instance has been created. All objects have attributes, but not all objects are fully identified by the collection of their attributes. when attributes are not enough to guarantee uniqueness, and when uniqueness is important to the specific object, then you just have domain-driven design entities.


In DDD,  As you go through the requirements and work out the domain model of a given bounded context, it is not unusual that you spot a few individual entities being constantly used and referenced together. In a domain model, the aggregation of multiple entities under a single container is called an aggregate, and therefore some entities and value objects may be grouped together and form aggregates.


DDD Value Types

To indicate a value object in .NET, you use a value type. The most relevant aspect of a value type is that it is a collection of individual values. The type therefore is fully identified by the collection of values(attributes), and the instance of the type is immutable. In other words, the attributes of a value object never change once the instance has been created, and, if it does, then the value object becomes another instance of the same type fully identified by the new collection of attributes. Value types are used instead of primitive types, such as integers and strings, because they more precisely and accurately render values and quantities found in the business domain.


e.g. let's suppose you have to model an entity that contains weather information. How would you render the member that indicates the outside temperature? For example, it can be a plain integer property. However, in this case, the property can be assigned just any number found in the full range of values that .NET allows for integers. This likely enables the assignment of values that are patently outside the reasonable range of values for weather temperature. The type integer, in fact, is a primitive type and too generic to closely render a specific business concept, such as an outside temperature. So here is a better option using a new custom value type. In the new type, you can have things like constructors, min/max constants, setters and getters, additional properties, for example, whether it's a Celsius or a Fahrenheit temperature, and even you can overload operators.


DDD Entities

Not all objects are fully identified by their collection of attributes. Sometimes you need an object to have an identity attribute, and when uniqueness is important to the specific object, then you have just entities. Put another way, if the object needs an ID attribute to track it uniquely throughout the context for the entire application life cycle, then the object has an identity and is said to be an entity.


Typically made of data and behavior

Contain domain logic, but not persistence logic


Concretely, an entity is a class with properties and methods, and when it comes to behavior, it's important to distinguish domain logic from persistence logic. Domain logic goes in the domain layer, model or services. Persistence goes in the infrastructure layer managed by domain services.

e.g. an order entity is essentially a document that lays out what the customer wants to buy. The logic associated with the order entity itself has to do with the content of the document, things like taxes and discount calculation, order details, or perhaps estimated date of payment. The entire process of fulfillment tracking status or just invoicing for the order are well outside the domain logic of the order class. They are essentially use cases associated with the entity, and services are responsible for the implementation.


DDD Aggregates

An aggregate is a collection of logically related entities and value types(A few individual entities constantly used and referenced together). More than a flat collection, an aggregate is a cluster of objects, including then relationships that you find easier to treat as a single entity for the purpose of queries and commands(Cluster of associated objects treated as one for data changes). In the cluster, there's a root object that is the public endpoint that the outside world uses to query or command. Access to other members of the aggregate is always mediated by the aggregated root. Two aggregates are separated by a sort of consistency boundary that suggests how to group entities together(Preserve transactional integrity). The design of aggregates is closely inspired by the business transactions required by the system. Consistency here means transactional consistency. Objects in the aggregate expose an overall behavior that is fully consistent with the business processes.


Now we have identified four aggregates, and the direct communication is allowed only on roots, only between roots. A notable exception here is the connection between order detail and product. Product, an aggregate root, is not allowed to access order detail and non-root elements directly. Communication is allowed, but only through the order proxy. However, it is acceptable that order detail, a child object, may reference an outside aggregate root, if, of course, the business logic just demands for that.




Domain Services

Domain services are instead responsible for things like cross-object communication(Cross-aggregate behavior), data access via repositories, and the use, if required, by the business of external services


Domain services, are made of special components, such as repositories that provide access to storage and proxies towards external web services



Misconceptions about DDD

1. Perceived simply as having an object model with some special characteristics

It now seems that DDD is nothing more than having an object model with the aforementioned special features. So the object model is expected to be also agnostic of data storage and then oversimplifying quite a bit. The database is part of the infrastructure and doesn't interfere with the design of the model. Database is merely part of the infrastructure and can be neglected.


=> Context mapping is paramount

=> Modeling the domain through objects is just one of the possible options


In DDD, identification and mapping of bounded contexts is really paramount. Modeling the domain through objects is just one of the possible options. So, for example, using a functional approach is not prohibited, and you can do good domain-driven design even if you use a functional approach to coding or even an anemic object model with stored procedures. 


=> The object model must be easy to persist

=> Persistence, though, should not be the primary concern

=> Primary concern is making sense of the business domain


For the sake of design, if you aim at building an object model, then persistence should not be your primary concern. Your primary concern is making sense of the business domain. However, the object model you design should still be persistent at some point, and when it comes to persistence, the database and the API you use it to go down to the database are a constraint and cannot always be blissfully ignored.


2. Ubiquitous Language is a guide to naming classes in the object model


=> Understand the language to understand the business

=> Keep language of business in sync with code


The ubiquitous language is the tool you use to understand the language of the business rather than a set of guidelines to name classes and methods. In the end, the domain layer is the API of the business domain, and you should make sure that no wrong call to the API is possible that can break the integrity of the domain. It should be clear that in order to stay continuously consistent with the business you should focus your design on behavior much more than on data. To make domain-driven design a success, you must understand how the business domain works and render it with software. That's why it's all about the behavior.



Persistence vs. Domain Model


Persistence Model : the model to persist data

Object-oriented model 1:1 with underlying relational data

Reliable and familiar to most developers

Doesn't include business logic (except perhaps validation)


You can express a data model looking at a bunch of SQL tables or using objects you manage through the object- relational mapper of choice, for example, Entity Framework. This is essentially a persistence model. It's object-oriented, and it's nearly 1:1 with data store of choice. Objects in the model may include some small pieces of business logic, mostly for validation purposes, but the focus of the design remains storage.


Domain Model

Object-oriented model for business logic

Persistable model

No persistence logic inside


It's still an object-oriented model, it's still a model that must be persisted in some way, but it is a model that doesn't contain any clue or a very limited awareness of the storage. The focus of the design here is behavior and business processes as understood from requirements.


In the end, it's a matter of choice. On one hand, you have a scenario in which objects are plain containers of data and business logic is hard-coded in distinct components. On the other hand, you have business logic expressed through processes and actual objects are functional to the implementation of those processes, and because of this, data and logic are often combined together in a single class.




What is behavior?

According to the dictionary it is the way in which one acts or conducts oneself, especially towards others. To move towards a behavior-centric design of the domain model, we need to set in stone what we intend by behavior.


Methods that validate the state of the object

Methods that invoke business actions to perform on the object

Methods that express business processes involving the object


In a model that exposes classes designed around database tables, you always need some additional components to perform access to those classes in a way that is consistent with business rules, yet your domain model class library has public classes with full get and set access, direct access, to the properties. In this case, there is no guarantee that consumers of the domain model will not be making direct unfiltered access to the properties with the concrete risk of breaking the integrity of data and model. A better way, a better scenario, is when you create things such as the only way to access the data is mediated by an API, and the API is exposed by the object themselves. So the API is part of the model. It's not an external layer of code you may or may not invoke. The containers of business logic are not external components, but the business logic that's the point is built right into the object. So you don't set properties, but you rather invoke methods and alter the state of objects because of the actions you invoked. This is a more effective way to deal and model the real world.


Aggregates and Value Types

In the beginning, when you start going through the list of requirements, whether you are in a classic analysis session or in an event storming session, you just find along the way entities and value types. As you go through, you may realize that some entities go often together, and together they fall under the control of one particular entity. 

When you get this, you probably have crossed the boundary that defines an aggregate. Among other things, aggregates are useful because once you use them you work with fewer objects and course grained and with fewer relationships just because several objects may be encapsulated in a larger, bigger container. And well-identified aggregates greatly simplify the implementation of the domain model.


Facts of an Aggregate

Protect as much as possible the graph of encapsulated  entities from outsider access

Ensure the state of child entities(contained objects) is always in a valid state according to the applicable business rules consistency

Actual boundaries of aggregates are determined by business rules.


How would you identify which objects form an aggregate? Unfortunately, for this there are no mathematical rules. Actual boundaries of aggregates are determined only and exclusively by analysis of the business rules. It's essentially primarily about how you intend to design the business domain and how you envision that business domain.

e.g. a customer would likely have an address, but customer and address are two entity classes for sure. Do they form an aggregate perhaps? It depends. If the address as an entity exists only to be an attribute of the customer. If so, they form an aggregate. Otherwise, they are distinct aggregates that work together.


Common Responsibilities (Associated with an Aggregate Root)

An aggregate root object is the root of the cluster of associated objects that form the aggregate. An aggregate root has global visibility throughout the domain model and can be referenced directly. It's the only part of the aggregate that can be referenced directly, and it has a few responsibilities too.


Ensure encapsulated objects are always in a consistent state

It has to ensure that encapsulated objects are always in a consistent state business-wise.

Take care of persistence for all encapsulated objects

Cascade updates and deletions through the encapsulated objects

Access to encapsulated objects must always happen by navigation

the root has to guarantee that access to encapsulated objects is always mediated and only happens through navigation by the root.


+ One repository per aggregate

Most of the code changes required to implement the responsibilities of an aggregate root occurs at the level of services and repositories, so well outside the realm of the domain model. Each aggregate root has its own dedicated repository service that implements consistent persistence for all of the objects.


But in terms of code, what does it mean to be an aggregate root?

An aggregate is essentially a logical concept. When you create a class that behaves as an aggregate, you just create a plain class. I would even say that you just have your entity classes, and then you upgrade one of those to the rank of an aggregate, but that mostly depends on how you use them, so being an aggregate is an aspect of the overall behavior and usage of the class rather than a feature you code directly.

However, especially when you have a very large domain model, it could be a good idea to use something that identifies at the code level that some classes are aggregates, and this can be a very simple, plain, in some cases, just a marker interface you can call IAggregate. The interface can be a plain marker interface, so an interface with no members, or you can add some common members you expect to be defined on all aggregates.


+ Constructor vs. Factory

It's interesting to notice the way in which classes in a domain model aggregates are initialized. Typically in an object-oriented language you use constructors. Constructors work, there's nothing really bad in terms of the functional behavior with constructors, but factories are probably better, because of the expressivity that factories allow you to achieve. In particular, I suggest you have a static class within an aggregate, you can call this class Factory, and this static class has a bunch of static public methods, CreateNew, CreateNew with Address, and as many methods as there are ways for you to create new instances of a given aggregate. The big difference between constructors and factories is essentially in the fact that on a factory class you can give a name to the method that returns a fresh new instance of the aggregate, and this makes it far easier to read the code. When you read back the code from the name of the Factory method, you can figure out the real reason why you are having a new instance of that class at that point.



Domain Services

Classes in the domain are expected to be agnostic of persistence, yet the model must be persistent. Domain services are a companion part of the domain layer architecture that coordinates persistence and other dependencies required to successfully implement the business logic.


Domain services are classes whose methods implement the domain logic that doesn't belong to a particular aggregate and most likely span over multiple aggregates. Domain services coordinate the activity of the various aggregates and repositories with the purpose of implementing all business actions, and domain services may consume services from the infrastructure, such as when they need to send an email or a text message.


Domain services are not arbitrarily pieces of code you create because you think you need them. Domain services are not helper classes. All actions implemented by domain services come directly straight from requirements and are approved by domain experts. But there's more. Even names used in domain services are strictly part of the ubiquitous language.


Let's see a couple of examples. Suppose you need to determine whether a given customer at some point reached the status of a gold customer. What do requirements say about gold customers? Let's say let's suppose that a customer earns the status of gold after she exceeds a given threshold of orders on a selected range of products. Nice. This leads to a number of distinct actions and aspects. First, you need to query orders and products to collect data necessary to evaluate the status. As data access is involved at this stage, this is not the right job for an aggregate to do. The final piece of information, whether or not the customer is gold, is then set as a Boolean value typically in any new instance of a customer class. The overall logic necessary to implement this feature, as you can see, spans across multiple aggregates and is strictly business oriented.

But here is another example. Booking a meeting room. What do requirements say about booking a room? Booking requires, for example, verifying the availability of the room and processing the payment. We have two options here, different, but equally valid from a strictly functional perspective. One option is using a booking domain service. The service will have something like an Add method that reads the member credit status, checks the available rooms, and then based on that decides what to do. The other option we have entails having a booking aggregate. In this case, the aggregate may encapsulate entities like room and member objects, which, for example, in other bounded contexts in the same application, for example the admin context, may be aggregate roots themselves. The actual job of saving the booking in a consistent manner is done in this case by the repository of the aggregate.


What's a repository?

In domain-driven design, a repository is just the class that handles persistence on behalf of entities and ideally aggregate roots. Repositories are the most popular type of a domain service and the most used. A repository takes care of persisting aggregates, and you have one repository per aggregate. Any assemblies with repositories have a direct dependency on data stores. Subsequently, a repository is just the place where you actually deal with things like connection strings and where you use SQL commands.


You can implement repositories in a variety of ways, and you can find out there are a million different examples, each claiming that that's the best and most accurate way of having repositories. I'd like to take here a low-profile position. There's nearly no wrong way to write a repository class.


public interface IRepository<T> where T : IAggregateRoot

{

//You can keep the interface a plain marker or

//you can have a few common methods here.


T Find (object id);

bool Save (T item);

bool Delete (T item);

}


You typically start from an IRepository generic interface and decide whether you'd like to have a few common methods there. But this, like many other implementations, are just arbitraries, and it's a matter of preference and choice. In domain-driven design, in summary, a repository is just the class that handles persistence on behalf of aggregate roots.



Events in the Business Domain (Why Should You Consider Events in a Domain Layer?)

Events in the context of the domain layer are becoming increasingly popular, so the question becomes should you consider events then? First and foremost, events are optional, but they are just a more effective and resilient way to express sometimes the intricacy of some real-world business domains.


Imagine the following scenario. In an online store application, an order is placed and is processed successfully by the system, which means that the payment is okay. Delivery order was passed and received by the shipping company, and the order was then generated and inserted in the system. Now what? Let's suppose that the business requirements want you to perform some special tasks upon the creation of the order. The question becomes now where would you implement such tasks?


The first option is just concatenating the code that implements additional tasks to the domain service method that performed the order processing. You essentially proceed through the necessary steps to accomplish the checkout process, and if you succeed you then, at that point, execute any additional tasks. It all happens synchronously and is coded in just one place.


void Checkout(ShoppingCart cart)

{

//Proceed through the necessary steps

...

if (success)

{

// Execute task(s)

}

}


What can we say about this code? It's not really expressive. It's essentially monolithic code, and should future changes be required, you would need to touch the code of the service to implement the changes with the risk of making the domain service method quite long and even convoluted. But there is more. This fact might even be classified as a subtle violation of the ubiquitous language. The adverb, when, you may find in the language typically refers to an event and actions to take when a given event in the business is observed.


What about events then? Events would remove the need of having whole handling code in a single place and also bring a couple of other non-trivial benefits to the table. The action of raising the event is distinct from the action of handling the event, which might be good for testability, for example. And second, you can easily have multiple handlers to deal with the same event independently. 


public class GoldMemberStatusReached : IDomainEvent

{

public GoldMemebrStatusReached(Customer customer)

{

Customer = customer;

}


public Customer Customer { get; set; }

}


The event can be designed as a plain class with just a marker interface to characterize that, and this could be as in the demo, IDomainEvent. This is analogous to event classes as we find them in the .NET Framework. To be honest, you could even use EventArgs, the .NET Framework root class for events as the base class if you wish. Not using .NET native classes is mostly done because of the ubiquitous language and to stay as close as possible to the language of the business.


void Checkout(ShoppingCart cart)

{

//Proceed through the necessary steps

...

if (success)

{

// Execute task(s)

Bus.RaiseEvent(new GoldMemberStatusReached(customer));

}

}


And then, once you have this event class defined in the checkout domain service method after you've gone through all of the business steps, you just raise the event with all the necessary information you want to pass along. Yes, but how would you dispatch events?


There's a bus. The bus is an external component typically part of the infrastructure, and it is acceptable in general that a domain model class library has a dependency on the infrastructure. The bus allows listeners to register and notify them transparently for the code. The bus can be a custom class you create, or it can be some sort of professional commercial class like end service bus or maybe rebus. There is more flexibility in your code when you use events as you can even record events and log what happened and can add and remove handlers for those events quite easily.


Events are gaining more and more importance in software design in architecture these days, well beyond the domain model as a way to express the business logic. Events are part of the real world and have to do with business processes much more than they have to do with business entities. Events help significantly to coordinate actions within a workflow and to make use case workflows a lot more resilient to changes. This is the key fact that is today leaning towards a different supporting architecture, event sourcing, an alternative to the domain model supporting architecture.



Anemic Models

Anti-pattern because "it takes behavior away from domain objects"


The domain model pattern is often contrasted to another pattern known as the anemic domain model. For some reason, the anemic domain model is also considered a synonym of an anti-pattern. Is it true? Sad that software architecture is the triumph of shades of gray and nothing is either black or white, I personally doubt that today the anemic domain model is really an anti-pattern. In an anemic domain model, all objects may still match conventions of the ubiquitous language and some of the domain-driven design guideline for object modeling like value types over primitive types and relationships between objects.


The inspiring principle of the anemic domain model is that you have no behavior in entities, just properties, and all of the required logic is placed in a set of service components that altogether contain the domain logic. These services orchestrate the application logic, the use cases, and consume the domain model and access the storage.


Again, is this really an anti-pattern today with the programming tools of today? Let's consider Entity Framework and Code First. When you use Code First, you start by defining an object model. The model you create, is it representative of the domain? I'm not sure. I'd say that it is rather a mix of domain logic and persistence. The model you create with Code First must be used by Entity Framework, so Entity Framework must like it. To me, this sounds more like a persistence model than a domain model. Sure you can add behavior, read methods to the classes in the Code First model, and there is no obvious guarantee, however, that you will be able to stay coherent with the requirements of the ubiquitous language and still keep Entity Framework happy. And in case of conflicts, because you still need persistence, Entity Framework will win, and compromises will be in order. The database at the end of the day is part of the infrastructure, it doesn't strictly belong to any model you create, but because of the persistence requirement it is still a significant constraint you cannot ignore. An effective persistence model is always necessary because of the functions to implement and because of the performance. Sometimes when compromises are too expensive you might want to consider having a second distinct model in addition to persistence, so you want to distinguish between the domain model and persistence model and use adapters to switch between the two. An alternative, you can completely ignore the domain model, keep your persistence model you create with Code First DB-friendly, and then use different patterns than the domain model pattern to organize the business logic of the application. In the end, having implementing the domain model pattern is not at all a prerequisite for doing good domain-driven design. And the model you end up with when using Code First and Entity Framework is hardly a domain model. It is a lot more anemic then you may think at first.



Beyond Single All-encompassing Domain Models

This is close to be a Murphy law. If a developer can use an API the wrong way, he will. How to avoid that? With proper design, I would say. All of the facts explained in Eric Evan's book about domain-driven design written about a decade ago have the word object and implicitly refer to an object- oriented world, the domain model. So far in this course I tried to push hard the point that the foundation of domain-driven design is agnostic of any development paradigm in much the same way it is agnostic on the persistence model. I believe that the real direction we're moving today is pushing the same idea of a domain model, single, all-encompassing model for the entire business domain to the corner. I rather see that it is more and more about correctly identifying and rendering business processes with all of their related data and events. You can certainly do that using classes that, like aggregates, retain most of the business logic and events, so you can certainly model business processes using the object-oriented paradigm. But, and that's really interesting, you can also do that today using functional languages. If you search around, you will find a lot of references already about using F# to implement business logic. Many of the references are still vague, but some points emerge clearly. With a functional approach, for example, we don't need to go about coding ourselves restrictions in a domain model such as using value types. That's the norm in a functional language. And also composition of tasks via functions often result naturally in so simple code that it can even be read to and understood by domain experts. The foundation of domain-driven design at the very end of the day is that ubiquitous language as a tool to discover the business needs and come up with a design driven by the domain in which presentation commands places orders, orders commands are executed, and somewhere, somehow, data is saved. For this way of working for this model, more effective practices and architectures are emerging. One is CQRS.



출처

이 모든 내용은 Pluralsight에 Dino Esposito가 올린 'Modern Software Architecture: Domain Models, CQRS, and Event Sourcing'라는 강의의 네번째 챕터를 듣고 정리한 것입니다(https://app.pluralsight.com/library/courses/modern-software-architecture-domain-models-cqrs-event-sourcing/table-of-contents). 제가 정리한 것보다 더 많은 내용과 Demo를 포함하고 있으며 최종 Summary는 생략하겠습니다. Microsoft 지원을 통해 한달간 무료로 Pluralsight의 강의를 들으실 수도 있습니다.

AND

ARTICLE CATEGORY

분류 전체보기 (56)
Programming (45)
MSDN (4)
개발노트 (2)
reference (5)

RECENT ARTICLE

RECENT COMMENT

CALENDAR

«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

ARCHIVE