Domain Driven Design
Having read a lot of discussion on DDD, I feel there is a misleading perception. Anyhow, this is just my experience.
Most of the discussion and examples in the DDD discussion are related to database persistence. Actually, in my experience, this is the least concern is DDD. The DAO design one way or another doesn't impact a complex application too much. I am a little bit tired of quoting small applications, so I am going to explain a large application, financial applications I've done over the years in different cases.
Thinking in the financial application domain(I don't claim I am an expert in this domain, in fact I think I am far away from it), the domain is the following. There are a lot of financial instruments/securities, such as stock, bond, derivatives that are traded every day. So we have a model for instruments, and there are different types as well, such as cash instrument(USD, EUR, etc), bond instruments, such as government bonds, corp bonds, mortgage, etc, and derivatives, such as forward/future/options/swaps. There is another category that is not tradable but closed related, such as interest rates, fx rates, and index etc. Interest rates are not traded, but derivatives on interest rates(called IR derivatives) are traded. All instruments have different attributes, which are divided into two main categories, indicative(static) or analytic. Indicatives are not senstive to price/quote, analytics does. For example, a stock ticker(name), such as IBM, is indicative. analytics, such as the GREEKS(delta, gamma, etc) are analytics(risk related). Derivatives can be viewed as contracts(if we have more than one contract, we called them derivative legs)
Instruments have events, all kinds of events. For example, a bank CD has interest payments, say semiannual, a stock can have a split event, a bond can have a maturity event, a credit relate swap(such as CDS) can have a default event. They are related to instruments, but modeling these events need the next concept, transaction.
Financial transactions are more than buy and sell, but buy and sell are pretty good examples. When we model a general transaction, we have an instrument, quality, dates(record date, settle date, effective date, etc). For example, we buy 100 shares of IBM stock today, the transaction will be settled in T+1(tomorrow). If we buy a US treasury bond, the settle date has impact on the interest payment. Other types of transactions are deposite, withdral(cashes), dollar roll, interest payment, matured(for bonds), expired, etc. In fact, we can use transactions to generate new instruments via customized cash flow. So instruments and transactions are interactive to each other.
The next concept is positions. Say we went to a bank a year ago and bought a 2 year CD which has a 5% interest annually for 1000 USD. This CD pays interest quarterly. Now, after a year we bought the CD, we should have 4 interest payments of 5% / 4(quarterly) = 1.25% of 1000, i.e., 12.50. So our position now is a CD(worth 1000, but this is not cash) plus 4 interest payments totally 12.50 * 4 = 50(this is cash). So transaction wise, we have one buy and 4 interest payments; but position wise, we have one cd worth 1000, and 50 USD cash. This means we need to settle all transactions (user transactions and instrument events) before today to combine all worths if possible. Sometimes it's not appropriate to combine positions, for example, if one cash position is in USD, another is in EUR, unless users want to combine them, we normally don't(if user specifies to combine them to a certain currency, then we need fx rate to convert one or both of them and then combine them).
Normally, we use portfolios(or books, i.e., trade books) to manage transactions. It's basically a placeholder to hold/categorize transactions. And sometimes we trade this porfolios, in this context we have a basket, basket is more associated with group of instruments, rather than transactions. In a sense, we can say indices, such as S & P 500 index, are baskets, though we don't call that because they are standard indices. Baskets are more for customized, instrument oriented, weighed in various ways.
So these are the basic building blocks, instrument, transaction/trade, portfolios/books. They are saved in databases, caches or files. Normally, we build DAO interfaces to do the CRUD. On top of these DAO layer, we build logic to compute the current positions(or as of some date positions). The logic is very complex because of different instruments and different transactions. Each particular case is not rocket science, but so many of different cases and more to add can and often do create a big maintanence problem. To make things worse, we need to expand those baskets/indices to its constituents. Due to the huge size, we need to the data loading to be really really fast. Some kind of cache, such as distributed case is almost unavoidable. The DDD is not designed such that each transaction is loading itself, otherwise loading 250K transactions will take forever. However, once the data loading is done, the DDD starts working. I have to think hard which field should be in the transaction, which field should be in the instrument, a plain layout of fields simply works less effectively and error prone.
The above consists of one component, the next component is the market data. A stock has quotes, open, close, high, low, every 1 sec, 1 min, or daily, etc. Almost all instruments have some kind of market data that we have to deal with, they are used to price instruments. The biggest problem is the "versioning". A stock quote two hours ago is likely different from the quote two minutes ago. Some times we may need imaginary data for risk testing. So the versioning plays a central role and is crucial for analytics. Though most of the time we use timpstamps, but it's not always working, since 5pm in New york is different from 5pm in Beijing. We need to massage the market data as well, such as generating yield curves, risky curves, backout volitility surfaces, etc.
The next component is pricing, we want to use the market data to price a trade/position as of certain date according to some analytic models. We have to know what something worths before we can trade it. This is where the fun part starts because a lot of math is involved. Building a solid, flexible code base is crucial for expansion.
The next component is risk, i.e., what if something is changed in the market, where we are heading. Say, if interest rate is changed by 1 base point(100 base point = 1%), then how much change is in our positions/trades. Since there are quite a few factors can be changed, we may want to know if a collection of these factors change at the same time then what happens. This leads to scenerio analysis. There are a few major problems in here. One is performance. We need to run many reports on millions of trades for risk control system. The second problem is new scenarios, here is where DDD shines, a good design can give users a lot of powers to run customized scenarios and is easy for developers to add new features. For example, market data bumps(like the above 1 base point bump) can be designed to a very effective and highly reusable small components. The DDD approach works the best. There are many kinds of bumps, such as parallel bump, bump to a certain number, bump proportionally, what to bump(the underlying quote or the curve, or some function of the curve). The composition of bumps is also needed.
Another component is market structure, we group our trades in a certain way(by some attributes in the trades) to sum up some other attributes to see how the entire book is.
Another component is to exploit the market situation/condition to see whether it's suitable to trade.
Now we should have an idea on how DAO layer role is in the entire domain. Its weight is not significant as the rest of the components. These components take most of the efforts and time.(I don't mean to say we shouldn't make the DAO performant or less effective). This is where we spend most of the time to do DDD, make them align with business logic, extract and refactor interfaces(method signatures and interfaces) to align with business language, assuming we have all the data we need. Most of the time, we don't have concrete requirements, we need to interact with users to get details. Everyday is a new day because the market is changing and evolving rapidly.
I benefited over the years from OO, DDD, and most importantly from GoF: Use composition over inheritance whenever possible. This rule can never be over emphasized. Through compositions, we maximize reuse. Through DIP, we maximize flexibility.
If you have just a web application without intensive internal calculations, I don't think there is a need to go DDD. Keep things simple and don't overengineer it. The purpose of DDD is to make our lives simpler, not the other way. If we don't know where to put a piece of code, leave it anywhere you feel right. If it's wrong later, we can move it. In my work, most of the time, nobody knows what the right answer is. Through iterative design and refactor, we "uncover" the right answer. And we keep an open mind too, since the right answer is always relative - could be wrong later.
My coding rule is if a class has more than 200 lines, there is something wrong(well, I stole this from my friend Chris, my old rule is 500, but I am now convinced). If I can't write a unit test(not because I am lazy), then I can't expect anyone else to use it. There are some exceptions, especially in the numerical computing area, but even in there if we do it right(through compositions), 200 lines can be done. Everytime when I go below 200 lines, I have a better understanding on the subject, especially after I write unit tests. Just a good practice.
My no.1 design guideline is composition, I want to make sure it can be composed into a bigger component. This is where I benefit most. For example, pricing is just one component, but we need to make sure it can be composed since we need this in risk - bump something, price it, calculate the difference ... . risk need to be composable since we need this in the scenario analysis. scenario analysis needs to be composable since it's needed in testing market condition ... .
This is more like building a house, keep adding something on top of something while keeping pluggable holes on the sideways.