I'm looking for insight/ papers/ articles, etc. whether a fully declarative Domain Model (as per DDD) is possible.
For example:
Validation can be declarative (lot's of ORMs do this)
business flow logic can be declarative: having a DSL for defining a workflow / Finite State Machine / process manager / DDD Saga (whatever you want to call it) on Crud-operations, through ddd-repositories most likely
decision logic can be declarative. I.e: most of the time this boils down to simple conditionals
derived / calculated fields could be done declaratively but is as bit tricky, especially when this cascades. I.e: you'd have to keep a dependency graph on the calculated fields, etc. Still it can be done.
Any links to people having actually tried that, or some convincing couter-arguments why this can't be done?
p.s.: Please don't answer with "Yes it can be done, since a FSM is Turing-complete with enough memory bla bla"
"Everything is a nail if you hold a hammer"
Instead of asking if it is possible - ask:
What are the reasons I want to do this particular thing declaratively?
Data validation is a nice thing to do declaratively. You have a DTO, you add some attibutes, everything is clear and readable.
Business flow done declaratively... Reminds me of a great failure of Windows Workflow Foundation. Is anyone actually using it?
What is the benefit of making a behaviour-centric components in a declarative way? Imperative way seems to be well-fitted to this.
Decision logic... maybe. But again - why?
"derived / calculated fields could be done declaratively but is as bit tricky"
Why do you want to do tricky things when there is a way of achieving the same result with straightforward things?
So why?
Do you need to change the behaviour of the app in runtime by editing some config file? OK, go for it.
Do you have a generic domain that will be used by many clients and you need some reconfiguration to fit them? Ok, but you need to clearly distinguish the unchangeable core of your domain and the varying stuff. Don't try to make all the things configurable - you'll end up with Inner Platform syndrome.
Do you believe you can make a declarative language your client could use to change his domain without a need for a programmer? No. You will fail. I know a language which was supposed to be like this. An easy, declarative language that ordinary accountants would use to explore their data. It's called SQL :D
I fully concur with Bartłomiej Szypelow's remark. Regardless, I will try to answer your question.
A declarative language always depends on an imperative language for it to work. Take something as simple as arithmetic for example. When we ask for the outcome of 1+1 we first need to know how the 1 should be understood and how in that context the + operator can be calculated before the statement as a whole can be interpreted. This is one of the ways we are able to overcome complexity; you don't need to know how a plus operation is done to be able to use one.
From http://en.wikipedia.org/wiki/Imperative_programming:
Procedural programming could be considered a step towards declarative
programming. A programmer can often tell, simply by looking at the
names, arguments and return types of procedures (and related
comments), what a particular procedure is supposed to do, without
necessarily looking at the details of how it achieves its result. At
the same time, a complete program is still imperative since it 'fixes'
the statements to be executed and their order of execution to a large
extent.
In any domain the same applies. If you ask the flight attendant to reschedule your flight she knows what a flight and a passenger is and how to reschedule one. Creating a fully declarative flight scheduling domain model without describing how scheduling works is therefore impossible. Since all domain models contain operations/behavior it is therefore equally impossible to create a domain model in a declarative language unless your problem domain is not unique, for example when you have three flight companies that operate in similar fashion.
Back to why... you said:
Actually, the hypothetical reason why is actually to allow non-coders
to create a (trivial) backend by configuration
Declarative programming is not always simpler than imperative programming. Most of the time people tend to think in outcomes, but sometimes the outcomes are so varied that it is (much) simpler to think in steps. This is the reason why these different styles exist and continue to exist in the first place. The main difference between a coder and a non-coder is not that a non-coder tends to think in terms of outcomes instead of processes, but that the non-coder simply does not want to be bothered with the workings of a computer. The non-coder wants to focus on the problem. Depending on the domain a non-coder may be helped best with an imperative DSL, instead of an declarative DSL.
Related
For my homework, I have to write functional requirement of a game called downfall (see Wikipedia).
We have to make this game, but with not two sides but n (any number of) sides.
In an example solution (another game), the teacher writes the functional requirements, then writes what use case they belong to.
I have created a use case diagram in which I have the player as an actor, and ChooseDial, RotateDial and EndTurn as use cases:
What I dont understand are the following:
Is the number of players functional requirement?
Is the table having two sides a functional requirement?
Is the goal of the game (getting coins from top to bottom) a functional requirement?
Is a rule like coins must reach bottom in order a functional requirement?
If they are, what use case could they belong to? Is my use case diagram wrong?
I have no idea where to put these functional requirements, because I feel like they arent part of any of my use cases.
About requirements
First, let's handle the requirement question:
A functional requirement tells something about what a software shall do. Everything related to goal, the gameplay or the rules of the game, is a functional requirement.
A non functional requirement tells something about how the software shall be, for example how accurate, how performant, how easy to use, how easy to maintain. Your narrative shows no such requirements.
About use-cases
Use case driven software development methods start with the high-level user's goals that are captured in use-cases. Personally, I see only one such goal:
Very rare usage: a multiplicity on the actor side of the use case. This says that 2 or more instances of players are involved in an instance of the use case. Of course, this makes sense only for the game as a whole, not for individual actions (like you have in your diagram).
In your diagram, you have shown 3 use cases:
is EndTurn an independent goal that the user may freely decide to chose ? No ! It's what always follows a player action. So this is definitely not a use case.
you say that RotateDials extends ChooseDials. This means that a player could ChooseDials but not rorate it. Is this a valid scenario ?
if on the other hand you'd say that ChooseDials includes RotateDials, the latter would always happen. But then, wouldn't ChooseDial not be more than just choosing a dial ? Shouldn't it then be called PlayTurn ?
I could understand that for learning purpose, you'd want to decompose the Play game in more detailed use-cases. Typically, once the players try to reach their goal Play game, this might include sub-goals of Play turn. As long as it is goal-oriented, and not too detailed, this is ok. But do avoid simple functional decomposition (it doesn't help for being more user-driven, and use-cases are not functions ). And, above all, do not misuse a use-case diagram for trying to show a sequence of activities.
Requirement traceability
The use cases do not capture the full requirements. It captures the most enssential thing: the purpose of the system and the user goals.
When writing down the requirements, it's then useful to get guided by the use-cases and their narrative, and to trace-back every use-case specific requirement.
But of course, there are some general requirements as well. These are not specific to a particular use-case. Some are even common to all use case. Mark these as general requirements (e.g. use case *).
Requirements management (RM) can be tricky indeed. A requirement like The board must have two sides seems to be more involved in the design, rather than the use case. In such cases you could relate that to the boundary rather than a single use case. That will indicate it's some "global" requirement (similar to a non-functional requirement). Usually in a project you start with a more or less strange mix of requirements mixed in user stories. The business analyst (BA) has to comb that information and come up with decent use cases (synthesize the added values). The system architect (with the BA) will then go through requirements and use cases to come up with a (business) class model.
There are tons of books and procedures describing RM. Lots of seminars too. I think if you grasp the condensed idea above you're ready to start. It's a marathon to start...
Note - all quotes are from DDD: Tackling Complexity in the Heart of Software
First quote ( page 222 ):
Processes as Domain Objects
Right up front let's agree that we do not want to make procedures a
prominent aspect of our model. Objects are meant to encapsulate the
procedures and let us think about their goals or intentions instead.
What I am talking about are processes that exist in the domain, which
we have to represent in the model. When these emerge, they tend to
make for awkward object designs.
The first example in this chapter described a shipping system that
routed cargo. This routing process was something with business
meaning. A Service is one way of expressing such a process explicitly,
while still encapsulating the extremely complex algorithms.
Second quote ( pages 104,106 ):
Sometimes, it just isn't a thing. In some cases, clearest and most
pragmatic design includes operations that do not conceptually belong
to any object. Rather than force the issue, we can follow the natural
contours of the problem space and include Services explicitly in the
model.
When a significant process or transformation in the domain is not a
natural responsibility of an Entity or Value Object, add an operation
to the model as a standalone interface declared as a Service. Define
the interface in terms of the language of the model and make sure the
operation name is part of the Ubiquitous language.
I can't figure out whether in first quote author is using the term "processes" to describe the same type of behavior ( which should also be encapsulated within a Service ) as in the second quote, or is the term "processes" used to describe a rather different kind of behavior than one he's describing on pages 104, 106?
Thank you
The concepts are pretty close but to me, the first quote looks more like it's about large real-world domain processes that would exist without the software (e.g. "a cargo routing process").
Second one is less clear but I think it describes smaller operations/processes/transformations taking place in the modelled version of the domain.
While the first kind should immediately click as "Service" right from early analysis stages, the latter is more subtle and could take more time to be distinguished from regular entity behavior - you could have included it in an entity at first but realize it doesn't fit that much in it as you refine the model.
I think in p. 222 he's talking about a specific kind of process. So like in the p. 102 quote, they can be implemented as services. However, some processes refer to actual domain processes and can benefit from explicit representation in the model. This may not be immediately obvious and can call for more sophisticated object-models beyond services.
How do I determine what should I add to my use case diagrams? 1 for each button/form? Should things like sort and search be included? Or are they under "list items" for example? Though, a list of items seems understood?
The Use Case diagram is intended to help define the high-level business tasks that are important, not a list of functions of the system. For example, a system for use in customer service might involve a research task of looking up information to help someone on a support call.
Most of the literature describes Use Cases as a starting point for defining what the system needs to accomplish. The temptation has always been to be as complete as possible; adding ever more details to define the use case down to a functional (code-wise) level. While it is useful to have a comprehensive understanding of the requirements, the Use Case diagram is not intended to provide that level of documentation.
One thing that makes the issue worse is the syntax which I've never seen used in a working project. It isn't that the terms aren't useful, it's due to the lack of consensus over when to use either term for a given use case. The UML artifacts expect a process that is more focused on the business language instead of the implementation language - and by that I do not mean a computer language. The tendency by some has been to approach the diagrams with a legalistic bent and worry about things like when to use for related use cases or how to express error-handling as exceptions to a defined list of process tasks.
If you have ever tried to work through the Automated Teller Machine (ATM) example, you'll know what I mean. In the solar system of UML learning, the ATM example is a black hole that will suck you into the details. Avoid using it to understand UML or the Object Oriented Analysis and Design. It has many of the problems, typical of real-world domains, that distract from getting an overall understanding even though it would make for a good advanced study.
Yes, code will eventually be produced from the UML artifacts, but that does not mean they have to be debated like a treaty in the Senate.
The OMG UML spec says:
Use cases are a means for specifying required usages of a system. Typically, they are used to capture the requirements of a system, that is, what a system is supposed to do. The key concepts associated with use cases are actors, use cases, and the subject. The subject is the system under consideration to which the use cases apply. The users and any other systems
that may interact with the subject are represented as actors. Actors always model entities that are outside the system.
The required behavior of the subject is specified by one or more use cases, which are defined according to the needs of actors. Strictly speaking, the term “use case” refers to a use case type. An instance of a use case refers to an occurrence of the
emergent behavior that conforms to the corresponding use case type. Such instances are often described by interaction specifications.
An actor specifies a role played by a user or any other system that interacts with the subject. (The term “role” is used
informally here and does not necessarily imply the technical definition of that term found elsewhere in this specification.)
Now most people would agree that business and user level interactions are the sweet spot, but there is no limitation. Think about the actors/roles being outside of the main system/systems you are focusing on. But in one view a system could be an actor, but in another the implementer of other use cases.
I am fairly new to this discussion but I HAVE to ask this question even at the risk of sounding 'ignorant'. Why is it that we now stress so much on 'DDD'. The more I look into 'DDD' the more complex it seems to make my application. Whereas modeling my domain with the database helps keep my application consistent across layers. Then I can use DAL Helpers such as SubSonic or L2S to easily access that model. What is so bad about this? Even in enterprise applications?
Why do we strive to create a new way of modeling our domain when we have a tried and tested one?
I am willing to hear from the purists here.
You can't sell an old methodology, because too many projects failed and too many people know the old methodology anyway. There has to be a new one to market.
If you're doing fine with the old way then use what works. Do pay attention to new stuff, as some really nice ideas come along. But that doesn't mean everything old is bad and stupid. Usually you can incorporate new ideas into the old models to a large degree.
There does come a time to make a move. Like I wouldn't do OOP with structures and function pointers. ;-)
This is actually a really excellent question, and the short answer is "you can." We used to do it that way, and there was a whole area of enterprise (data) modeling. In fact, the common OOD notations evolved from ERD.
What we discovered, however, was that data-driven designs like that had some difficulties, the biggest of them being that the natural structure for a data base doesn't necessarily match well to the natural structure for code.
OOD, to a great extent, derives from the desire to make it easier to find a code structure that has a couple of desirable properties:
it should be easy to think out the design
it should be robust under changes.
The ease to think out design comes originally from Simula, which used what we now think of as "objects" for simulation specifically; it was convenient in simulation to have software entities that correspond to the things you're simulating. It was only later that Alan kay et al at Xerox saw that as a more general structuring method.
The part about robustness under changes had many parents, but one of the most important ones among them was Dave Parnas, you wrote several papers that identified a basic rule for modularization, which I call Parnas' Law: every module should keep a secret, and that secret is a requirements that is likely to change.
It turns out that by combining Parnas' Law with the Simula idea of a "object" as corresponding to something that can be identified with the real world, you tend to get system designs that are more robust under requirements changes than the old way we did things. (Not always, and sometime you have to be crafty, as with the Command pattern. Most objects are nouns, thing that have persistent existence. In the Command pattern, the ideal objects are verbs, things you do.)
However, it also turns out that that structure isn't necessarily a good way to represent the underlying data in a relational database, so we end up with the "object relational impedance mismatch" problem: how to represent the transformation from objectland to database-land.
Short answer: if all you need is a CRUD system that allows users to edit data, just build an Access front-end to your back-end database (or use a scaffolding framework like you mentioned) and call it a day. You should be able to lop off 70% of your budget vs. a domain-driven system.
Long answer: with a data-driven design, what does the implementation of the business model look like? Usually after a couple years of building on new features to your application, you'll find that it's all over the place: tables, views, stored procedures, various application services, code-behind files, presenters/ViewModels, etc. with duplication everywhere. When you're having a conversation with the domain expert about a new feature they are requesting, you are constantly trying to translate from the business language into the language around your implementation, and it just does not translate.
What typically ends up happening is that you are forced to communicate with the business in terms of the implementation of the system, and the implementation becomes the "ubiquitous language" that the business and developers are forced to use when communicating. This has a wide range of consequences. The domain experts in the business start believing that they are experts in the implementation domain, and they start demanding features in terms of implementation rather than the business need they are trying to solve.
Also, you'll find that most data-driven implementations do not follow the "conceptual contours" of the domain, and the components of the system aren't very flexible in how they can be combined together to solve the problem, because they don't map one-to-one with concepts in the business model. When code isn't cohesive, changes and new features may require modifications all over your implementation.
Domain Driven-Design provides tools for making your implementation so closely resemble the business model that it's easy for everyone to speak the language of the business. It allows you to write "executable specifications" that test your implementation, but can actually be understood by your domain experts.
DDD teaches us to build our classes like their real-world prototypes.
So instead of using setters
job = new Job
job.person = person
job.since = time.Now()
job.title = title
we define well-named methods in our aggregation root
job = person.promote(title, /** since=time.Now() **/)
Now the tricky part
Assume we have an UI for an HR where he/she enters a new title via the HTML form and makes a typo like "prgrammer" (Of course in real application there'd be a select list, but here we have a text input), or selects a wrong date (like default today)
Now we have a problem. There are no typos in real world. Our John Doe is definitely a "programmer" and never a "prgrammer"
How do we fix this typo in our domain model?
Our Person has just promote, demote, fire, etc. methods, which reflect the HR domain model.
We could cheat a little bit and change the Job record directly, but now we have a Job.setTitle method, that doesn't reflect our domain model and also, setters are evil, you know.
That may look a little "academic", but that really bugs me when I try to build a good domain model for a complex application
Another side of DDD is invariants - "always valid" entity. And when you try to break this invariant (some rule) you must stop execution and say "loudly" adout this (throw exception). So, you need to have a list of valid titles and when you try to change title (does not matter how) to invalid state, you must throw some usefull exception.
To "fix" typo situations you must separate operations in your domain promote is one operation (it may check something, sent contratulation email :) and so on). And edit operation - just to edit some properties. So, the differenece is in logic of operations. You can't call promote without some preconditions (for example, required experience of worker), but you can call edit and fix worker's name because of type.
And usually this operations are separated between different users: only HR's can promote but a worker can edit his name, if it's wrong.
This solution is very complicated for such example, but it's always with DDD.
The main concept - separate operations. Each one with their own conditions, permissions, rules.
A question about invariants (rules).
If a client is purely entering data, then the underlying domain in this (bounded) context is not very deep. In these cases, it's fine to use a CRUD style application and allow titles to be changed (setTitle()).
Just make sure dependent BCs (e.g., billing, vacation planning, ...) where no such thing as "invalid data" exists, can react to changes in your CRUD context appropriately.
The application should enforce input correctness before it reaches the domain layer, no garbage input. If that means using a dropdown for the job titles then so be it. You can validate the title against existing titles.
In my company of 18 thousand employees, typo happens all the time. You are going to have to be pragmatic about this and accept that there will be setters in your code (one way or another)
Pragmatic thinking is very much at the core of the domain driven design, and is what keep things simple.
"Purity is good in theory, but in practice it can be very difficult to achieve, and sometimes you must choose the pragmatic approach" - Patterns, Principles, and Practices of Domain-Driven Design (2015)
"There are no typos in real world", I get what you mean, but that's not true, there are human mistakes in real world scenarios and they should be accounted for in your domain if they are frequent.
If data entry errors aren't frequent it may not be worth the extra modeling efforts and those could perhaps just get fixed directly in the DB. It also depends if the business wishes to learn something about those mistakes or not.
However, if data entry errors are frequent, it might be an indicator that the system is perhaps not offering enough guidance and the business may wish to learn more about those errors in order to make processes more efficient and less error-prone.
You may wish to implement an operation such as job.correctTitle(...), perhaps in a BC dedicated to data corrections? Also, it's probably very rare that each and every piece of information will be erroneous so corrective operations can be segregated. That means you probably do not need a job.correctAllInformation(...) kind of operation.
This whole scenario is very fictive since job titles would usually be managed in a separate BC from where they are used and they would probably get picked from a list, therefore typos would be less frequent, but you will always have to deal with data entry errors. Choosing the appropriate solution is not always easy and will vary from case to case, but try to stay pragmatic and not strive for the perfect model in every sphere of your domain.