views:

127

answers:

3

In an ASP.NET MVC application implementing the repository pattern I am curious if it is appropriate to place non-data related methods in the repository if they still pertain to the general focus of a given repository. For example, suppose a ProductsRepository that has methods for adding and deleting ProductImages which have a partial representation in the database and in the local file store as well. If a ProductImage needs to be deleted we need to delete a row from the database with a repository method and we also need to delete the files associated with that image from the storage medium. Do the IO operations belong in the repository, or is there a more appropriate location?

One thing I have been doing in a situation like the one I just described is providing static methods in my repository which give me the path to a given ProductImage by using the filename stored in the database and a pre-defined directory pattern to programatically generate it. Is this outside the intended use of a repository?


Edit

If such an operation does not belong in the repository, where should something like that go in an MVC pattern? It seems to me that it might make sense to have another layer between the Controller and the Repository that calls the Repository as needed and can be invoked statically from the Controller.

+2  A: 

I think the bigger concern over the repository pattern is the fact that you are violating single responsibility principle. Your class should have one responsibility, such as manipulating data in the database. You should have a different class deal with the File IO, and you can group the functions in a class one layer up.

There should only be one reason for a class to change, and a repository class that handles file IO and db calls will have two. A change to the file system layout, or a change to the database.

Edit

To address your edit question, here is how I would implement this in an MVC scenario (this is also assuming you are using some kind of dependency injection to make life easier).

// Controller class
public class ProductsController
{
    private IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService
    }

    public void RemoveImage(int productId, int imageId)
    {
        _productService.RemoveImage(productId, imageId)
    }
}

public class ProductService: IProductService
{
    private IProductRepository _productRepository;
    private IProductImageManager _imageManager;

    public ProductService(IProductRepository productRepository, IProductImageManager imageManager)
    {
      _productRepository = productRepository;
      _imageManager = imageManager;
    }

    public void RemoveImage(int productId, int imageId)
    {
        // assume some details about locating the image are in the data store
        var details = _productRepository.GetProductImageDetails(productId, imageId);
        // TODO: error handling, when not found?
        _imageManager.DeleteImage(details.location);
        _productRepository.DeleteImage(productId, imageId)
    }
}

Then you implement the IProductImageManager and the IProductRepository based on whatever interface makes sense with concrete implementations for your specific needs.

NerdFury
The repository does have a single responsibility... to provide access to the products data. How that's done is wrapped up in the repository itself, to my mind that's the point of the repository in the first place. If at a later date, and god forbid, the images are pulled into the database, this can be handled in the repository without impacting the application.
Lazarus
I disagree, you are giving it two responsibilities. One is managing data in the database, the other is managing images on the file system. You are at too high a level when you say that the responsibility is managing product data. You could also say that separation of concerns is missing in a solution like this. Part of the reason for the repository pattern is to separate and encapsulate getting data from some kind of data storage. If you have multiple data stores, then you have multiple concerns.
NerdFury
I like this answer a lot. Thank you :)
Nathan Taylor
Sorry to labour this point, I'm really trying to get my head around this. To me, the ProductService class now looks like a repository that has two subordinate repository objects. However, I'm not going to want to call ProductService to work with product actions that involve images but then call ProductRepository to deal with product actions that don't involve images. So isn't IProductService is going to have include all of IProductRepostory and won't it be in effect the repository from the viewpoint of the application using it.
Lazarus
It's fine, I'm enjoying the discussion. The truth is, there is no one size fits all solution. In many cases, a service class ends up being a pass-through wrapper for a repository, it's true. And in those scenarios, it might just be extra work. Consider the situation where you might want to also get comments or ratings or both about a product, or find related products. That business logic should not live in a controller action, it should be wrapped up in services IMO. The controller action might make a couple service calls which in turn might make several repository calls.
NerdFury
There is a pretty good debate about this topic on a blog post at devlicious by Derek Whittaker here: http://bit.ly/31jON1
NerdFury
+1  A: 

The repository is there to isolate the application from the concerns of how and where the data it's consuming is stored. On that basis, the repository is absolutely the right place to handle both the database and the file-based activities in this context.

Lazarus
what if they decided to store images using Amazon S3, now you have database calls, and Rest calls in the same class. There should be a service with a method that wraps both calls. One to remove the record from the database, and then if that succeeds, a call to delete the file can be made, and that class can handle the details about if the image is on the file system, in the cloud, or in the database. I see those as two different responsibilities and two different concerns. They should be separated in implementation.
NerdFury
So, if my images are in the database then that's fine, it's a single concern but if the images are stored elsewhere then you plan to make the location of the images a separate concern to the location of the rest of the Product data and pass responsibility for knowing that to the application?
Lazarus
No, the application can still be ignorant to where the images are stored. I would use an IImageManager interface with a 'DeleteImage' method. The app would need to use a concrete implementation of that interface that I would give to the calling application using Dependency Injection. I see your point, that they are related actions, and I'm not saying your approach is 'wrong', I'm saying that I think there are more fault tolerant ways of accomplishing this. You could make database calls, and file operations in your controller methods too, but you aren't for a reason. Separation of concerns.
NerdFury
I think the difficulty here is that I'm talking about concerns in domain-terms and you are looking at the concerns as technical implementations. Both right just in different contexts.
Lazarus
fyi -I replied to your comment on my answer (wanted to make sure you got a notification)
NerdFury
+2  A: 

I recently designed a new repository and struggled with this same question. I ended up including my additional methods in the repository.

Looking back at it now though, I feel that a better solution would have been to keep my repository more focused, and to have put my additional methods into a Service that integrated closely with my repository.

For your example above, you could then have a "DeleteProductImage" method in ProductsService that would call ProductsRepository.DeleteImage & then also handle deleting the images from the storage medium.

This keeps your Repository clean and focused on just the "DeleteImage" logic, while still giving you a single method you need to call ("DeleteProductImage") which takes care of calling the repository to delete the image while also handling interacting with the storage medium and any other things that may need to happen when an Image is deleted that aren't directly related to your repository.

Kwen
What I wonder here though is if I'm doing it that way should I not be using a service layer for all of my calls on a given model object and then it would fall on the service to call the repository if necessary? Otherwise I'd be creating a disparity between how my data layer gets talked to across the application.
Nathan Taylor
I'll be honest, this confuses me. I want to call DeleteProduct on the repository and have it deal with removing database record and/or files on the file system (where appropriate),. The key is that the repository is just that, it's between the application and the data irrespective of the storage medium.
Lazarus