11

There is Campaign Entity and for that, I have CampaignRepository which have this functions

  1. public IList FindAll();
  2. public Campaign FindByCampaignNumber(string number);

But now i want this criterias -:

  1. Find campaigns that are created today.
  2. Find campaigns that are created in this month
  3. Find top 5 latest campaigns.
  4. Find campaigns that are created in this year.

So for all these campaigns filters,

Do i create separate function for each of them in repository ?

and implement like this way.

Getall campaigns and then filter required campaigns, but i do not want all campaigns. While searching in google i find this solution's

1: http://russelleast.wordpress.com/2008/09/20/implementing-the-repository-and-finder-patterns/

Is there any method i can avoid multiple functions or do i go ahead and create seperate functions for each of this filter ?

Oded
  • 489,969
  • 99
  • 883
  • 1,009
kamal
  • 739
  • 1
  • 9
  • 21

4 Answers4

8

Have you considered implementing Specification pattern in your application? Maybe it looks like an overkill, but it may prove useful if your app will have some complex user filter options.

class CampaignSpecification
{
    public CampaignSpecification Number(string number);
    public CampaignSpecification DateBetween(DateTime from, date to);
    public CampaignSpecification Year(DateTime year);
} //I have omitted all the AND/OR stuff it can be easily implemented with any SQL like query language

Here is an example how loading from the repository may look like

var  campaignList = CampaignRepository.load(
            new CampaignSpec()
                .Number("2")
                .Year(DateTime.Now);

Also I'd like to add that it depends much on what kind of data access solution you are using, it makes implementing easier when you know what kind of API you will be using(Criteria API, SQL or whatever) so you can tweak your Specification interface to make its implementation simpler.

UPDATE: if you are implementing specifications in .NET using linq and nHibernate please check out http://linqspecs.codeplex.com/

Boris Treukhov
  • 17,493
  • 9
  • 70
  • 91
  • If i go with this solution, http://russelleast.wordpress.com/2008/09/20/implementing-the-repository-and-finder-patterns/ – kamal Aug 07 '11 at 21:45
  • http://russelleast.wordpress.com/2008/12/11/creating-fluent-finders-and-repositories-part-1/ also continuation of link given above – kamal Aug 07 '11 at 21:46
  • the second link is better as it shows *Method Chaining* part answering your question :) Of course it's up to you to decide. – Boris Treukhov Aug 07 '11 at 22:04
  • also checkout http://stackoverflow.com/questions/877034/linq-predicatebuilder-multiple-ors-newbie-q and http://www.albahari.com/nutshell/predicatebuilder.aspx – Boris Treukhov Aug 07 '11 at 22:25
  • How do i implement specification. Do i fetch all campaigns and then loop on it and then apply specification on each entity. Is that correct. – kamal Aug 08 '11 at 17:49
  • @kamal: Either way is fine from DDD perspective. But usually you convert your Specification to SQL/HQL query or NHibernate criteria in the repository implementation. Note that repository interface belongs to domain and implementation belongs to data access layer. – Dmitry Aug 08 '11 at 19:53
  • Fetching all campaigns is big no-no. Spec. implementation depends(despite Spec. belongs to the domain level)on how you access your database:if you are using LINQ2SQL the quickiest solution is to make your specification LINQ aware(LINQ is a part of C#, it will at least simplify IsSatisfiedBy implementation)and put the predicates into single expression(I supose such expression 'll be converted into decent sql query by linq2sql)But beware exposing LINQ expressions directly in your Spec implementation it will not only make your specs useless but also make impossible implementing alternative repos. – Boris Treukhov Aug 08 '11 at 20:11
  • Ok so I could not fit into 600 characters )) I think you should specify the ORM(ADO .NET, plain sql, Entity Framework, NHibernate)and the DB engine you are using - because it's obvious, that if you are not using a server with extensive LINQ support (MS SQL) all LINQ stuff will be useless. – Boris Treukhov Aug 08 '11 at 20:44
  • I think you are passing specification object into repository, but how you are converting specification object into hql. Do you have any sample code or link where can i see. It would be nice for me. – kamal Aug 09 '11 at 12:48
  • Can you help me in this http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/, he told us that we should not have generic repository, but we should have FindUserByName in repository instead of FindUsers(Ispecification specification). Is there not function explosion in repository. – kamal Aug 11 '11 at 19:15
  • here's a quote from Evans DDD Book: _The SPECIFICATION-based query is elegant and flexible. [. . .] Even a REPOSITORY design with flexible queries should allow for the addition of specialized hard-coded queries. They might be convenience methods that encapsulate an *often-used query* or a query that doesn't return the objects themselves, such as mathematical summary of selected objects. Frameworks that don't allow for such contingencies tend to distort the domain design or get bypassed by developers_. – Boris Treukhov Aug 11 '11 at 20:00
3

I would go with creating two Specifications: TopCampaignSpec and CampaingCreatedSpec.

var spec = CampaignCreatedSpec.ThisYear();
var campaigns = CampaignsRepository.FindSatisfying(spec);

CampaingCreatedSpec can also be replaced with more generic DateRange class if you need this functionality elsewhere:

var thisYear = DateRange.ThisYear();
var campaigns = CampaignsRepository.FindByDateRange(spec);

I also highly recommend staying away from 'generic' repositories and entities. Please read this

From DDD perspective it does not matter whether data access code is implemented as SQL/HQL/ICriteria or even web service call. This code belongs to repository implementation (data access layer). This is just a sample:

public IList<Campaign> FindByDateRange(CampaignCreatedSpec spec) {
    ICriteria c = _nhibernateSession.CreateCriteria(typeof(Campaign));
    c.Add(Restrictions.Between("_creationDate", spec.StartDate, spec.EndDate));
    return c.List<Campaign>();
}
Dmitry
  • 17,078
  • 2
  • 44
  • 70
  • If i go with this solution, russelleast.wordpress.com/2008/09/20/… – kamal 1 min ago edit – kamal Aug 07 '11 at 21:47
  • http://russelleast.wordpress.com/2008/12/11/creating-fluent-finders-and-repositories-part-1/ also continuation of link given above – kamal Aug 07 '11 at 21:48
  • Do i create two methods in repository for two specification or do i create it in application layer, and get all campaigns and then apply specification there ? – kamal Aug 07 '11 at 21:59
  • Two methods in repository. Specification is part of you model / business logic layer. But getting the data based on this specification is the responsibility of data access layer (your repository implementation). – Dmitry Aug 07 '11 at 23:53
  • dmitry you told me to convert specification into hql or nhibernate criteria, I don't know how to do that . Do you have any code or link. Meanwhile i am applying specification like this. http://devlicio.us/blogs/casey/archive/2009/03/02/ddd-the-specification-pattern.aspx – kamal Aug 09 '11 at 09:40
  • Thanks alot for your code. Between for finding answer i came to this article of greg young http://stackoverflow.com/questions/6975378/filters-in-ddd-repository which discourages what we are doing. – kamal Aug 09 '11 at 17:55
2

Here is how I would do this:

class Campaigns{
  IEnumerable<Campaign> All(){...}
  IEnumerable<Campaign> ByNumber(int number){...}
  IEnumerable<Campaign> CreatedToday(){...}
  IEnumerable<Campaign> CreatedThisMonth(){...}
  IEnumerable<Campaign> CreatedThisYear(){...}
  IEnumerable<Campaign> Latest5(){...}

  private IQueryable<Campaign> GetSomething(Something something){
    //used by public methods to dry out repository
  }
}

Reasoning is simple - it matters by what You are interested to look for campaigns (that knowledge is part of Your domain). If we explicitly state functions to reflect that, we will always know it.


Is it appropriate to add all this methods in campaign repository ?

I don't see anything wrong with that.

Arnis i want some code, how u implementing Created today function in domain itself, Are you injecting repository here in this function ? Thanks for your cooperation

I wouldn't implement CreatedToday function in my domain. It would sit in repository and repository implementations should not be concern of domain. If You mean how I would use Campaign repository and if it should be used from domain - no, it should not be used from within of domain. If You mean if I would inject repository inside of repository - You are listening too much of xzibit.

Arnis Lapsa
  • 45,880
  • 29
  • 115
  • 195
  • Is it appropriate to add all this methods in campaign repository ? – kamal Aug 08 '11 at 16:16
  • Arnis i want some code, how u implementing Created today function in domain itself, Are you injecting repository here in this function ? Thanks for your cooperation. – kamal Aug 08 '11 at 17:24
  • How about using specification ? – kamal Aug 08 '11 at 18:58
  • @Arnis. The code you provided may start suffering from 'method explosion' if you add few more methods to it. In DDD you deal with this using Specification pattern. – Dmitry Aug 08 '11 at 19:58
  • Can you help me in this http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/, he told us that we should not have generic repository, but we should have FindUserByName in repository instead of FindUsers(Ispecification specification). Is there not function explosion in repository. – kamal Aug 11 '11 at 19:16
  • @kamal Don't know. Somehow never faced such a "function explosion" that would bother me. There's always like max 10 different ways I want to look for aggregate roots. Doesn't look like a problem to me. – Arnis Lapsa Aug 12 '11 at 09:24
-1

You should be able to do all of the above with the following repository method:

List<Campaign> findCampaigns(Date fromCreationDate, Date toCreationDate, int offset, Integer limit) {

   if (fromCreationDate != null) add criteria...
   if (toCreationDate != null) add criteria...
   if (limit != null) add limit...
}

This is how I do it and it works very well.

Piotr
  • 4,813
  • 7
  • 35
  • 46
  • OK, with your method that also came to my mind, but now suppose my criteria increases then do i add more params in findcampaigns method or do i separate out another method. – kamal Aug 07 '11 at 22:23
  • Yes you simply add more parameters. But remember that if you do it the other suggested way, you can almost as well use JPQL/JPA criteria API directly, plus having a find method that is too flexible will make it very difficult to test if you are doing integration testing. – Piotr Aug 08 '11 at 05:39
  • 1
    Ok, same principle would apply. By creating some kind of dynamic filtering you will make life very difficult for yourself. Testing will be difficult etc. – Piotr Aug 08 '11 at 14:09
  • 2
    @Piotr: Having long list of parameters is a well known code smell. It is hard to read, understand, hard to test etc. Do not advice it to anyone. – Dmitry Aug 08 '11 at 19:49
  • @Dmitry: In this case, I do not agree. It is easy to test and understand. Of course, if you end up with too many parameters then you might end up benefiting from some other solution but in this case I think 4 parameters is not alot. – Piotr Aug 08 '11 at 22:55
  • 1
    @Piotr: 4 is already pushing the limit. And you suggest to "Yes you simply add more parameters". This is especially bad since some parameters should be ignored in some cases. – Dmitry Aug 08 '11 at 23:26
  • dmitry you told me to convert specification into hql or nhibernate criteria, I don't know how to do that . Do you have any code or link. Meanwhile i am applying specification like this. http://devlicio.us/blogs/casey/archive/2009/03/02/ddd-the-specification-pattern.aspx – kamal Aug 09 '11 at 09:40
  • @Dmitry: You have a valid opinion, but in this case I do not agree. However, I have not tried the DDD Specification way of doing things, so I can not say for certain my way is better - just that it works very well for me. – Piotr Aug 09 '11 at 09:54