Well, you can see a good example in the Spring Data Framework which is based on the concept of repositories.
There you will see repositories only deal with the data store, and rarely contain any business logic (this is reserved for the service layer). So, for instance, you take a look a their design you will see they have a CRUDRepository interface which exposes methods to create, destroy and recover entities (among other things). There is also a PagingAndSortingRepository that adds extra functionality for precisely that, sorting and paging results, etc, etc.
So, this framework is perhaps a good place to study a good repository design.
As far as I know, many of the concepts implemented by the Spring Data Framework, come from a great book called Domain-Driven Design: Tackling Complexity in the Heart of Software, the book has an entire section dedicated to Repository design.
You may consider getting a copy of it.
A small excerpt from the book explains:
The REPOSITORY pattern is a simple conceptual framework to
encapsulate those solutions and bring back our model focus.
A REPOSITORY represents all objects of a certain type as a conceptual
set (usually emulated). It acts like a collection, except with more
elaborate querying capability. Objects of the appropriate type are
added and removed, and the machinery behind the REPOSITORY inserts
them or deletes them from the database. This definition gathers a
cohesive set of responsibilities for providing access to the roots of
AGGREGATES from early life cycle through the end.
Clients request objects from the REPOSITORY using query methods that
select objects based on criteria specified by the client, typically
the value of certain attributes. The REPOSITORY retrieves the
requested object, encapsulating the machinery of database queries and
metadata mapping. REPOSITORIES can implement a variety of queries that
select objects based on whatever criteria the client requires. They
can also return summary information, such as a count of how many
instances meet some criteria. They can even return summary
calculations, such as the total across all matching objects of some
numerical attribute.
A REPOSITORY lifts a huge burden from the client, which can now talk
to a simple, intention-revealing interface, and ask for what it needs
in terms of the model. To support all this requires a lot of complex
technical infrastructure, but the interface is simple and conceptually
connected to the domain model.
Therefore:
For each type of object that needs global access, create an object
that can provide the illusion of an in-memory collection of all
objects of that type. Set up access through a well-known global
interface.
Provide methods to add and remove objects, which will encapsulate the
actual insertion or removal of data in the data store. Provide methods
that select objects based on some criteria and return fully
instantiated objects or collections of objects whose attribute values
meet the criteria, thereby encapsulating the actual storage and query
technology. Provide REPOSITORIES only for AGGREGATE roots that
actually need direct access. Keep the client focused on the model,
delegating all object storage and access to the REPOSITORIES.
I think you got Fowler wrong, he is advocating 3, not 1 (see http://www.martinfowler.com/bliki/AnemicDomainModel.html). Note that he is not talking about "business logic inside entity classes", he is talking about "business logic inside domain classes". And Service objects are domain objects.
In your example above: if you need to calculate the orders total, why not create a service class "OrderTotalCalculator"? Name the class accordingly for its purpose (and not OrderService or OrderManager, which would actually hide the purpose).
You already mentioned one criteria by yourself for when to introduce such a service class, and when business logic can stick inside entities: when "a bunch of other classes" is involved, then placing the logic in a specific entity is probably not the best design. You also mentioned the single-responsibility principle, so let yourself guide by this.
However, this does not necessarily lead to option 2 - since to my experience there are typically enough business operations for which an entity can take the full responsibility for itself, without introducing an extra service class for every operation. This will typically lead to a lean (but not anemic) entity classes.
The third option is probably the right one, but it is also devoid of any useful info.
Yes, it is probably the right one, but what do you mean by "is devoid of any useful info"? Do you miss an instructions manual on this? Software development is a design process, not like working at an assembly line. So making the right design decisions is not always easy and needs a lot of experience. And naming things is actually one of the hardest tasks.
Best Answer
CRUD is an acronym that stands for Create, Read, Update and Delete. Those are the four basic operations that you can perform on a database tuple. But there's always more to business applications than creating, reading, updating and deleting database records.
Let's start with some basic definitions, and then look at a couple of examples and see how those definitions map to the examples, and how they map to actual software.
Business logic or domain logic is that part of the program which encodes the real-world business rules that determine how data can be created, stored, and changed. It prescribes how business objects interact with one another, and enforces the routes and the methods by which business objects are accessed and updated.
Business Rules describe the operations, definitions and constraints that apply to an organization. The operations collectively form a process; every business uses these processes to form systems that get things done.
Now, let's work with some examples.
Transferring money from one checking account to another
First, what are the things that you need to know (input)?
What are some of the "business rules" that must be applied?
By "atomic," I mean that the transaction must completely succeed or it must completely fail. You can't have account transactions where money is taken out of one account without arriving in the other (money disappears), or money is deposited into an account, but not debited from another account (money magically appears from nowhere).
Ordering something from Amazon.
What do you need to know?
What happens after the order is placed?
Items are pulled from stock
On hand quantities are debited
Items are packaged for shipment
Out of stock items are backordered
Items that drop below minimum quantities are ordered
One shipment or two?
An invoice/shipping list is printed, and placed with the order
..etc.