views:

265

answers:

3

Hi

Im new to the DDD thing. I have a PROFILE class and a PROFILE REPOSITORY CLASS. The PROFILE class contains the following fields -> Id, Description, ImageFilePath

So when I add a new Profile, I upload then image to the server and store the path to it in my db.

When I delete the profile, the image should be removed from my file system aswell.

My Question:

Where do I add logic for this. My profile repository has a Delete method. Should I add this logic here. Or should I add a service to encapsulate both actions.

Any comment would be appreciated...

Thanks

+1  A: 

You have two different "actions" related to the images. You have a "physical" process and a "logical" process. The logical process is persisting the information about the image into the domain repository, since it is part of the domain. The physical process of add (and delete) are a prerequisite to the logical process.

Taking a step back, the physical process is completely independent of the logical process, but the opposite is not true. You obviously do not want to persist meta-information about the image (in the domain) if the image was not saved. Also, you don't want to remove the information from the domain if you cannot remove the physical file.

The domain should contain the information required to remove the logical instance of the image from the datasource. Think of the domain as a physically separate application. In this case, the domain has no actual knowledge that the data it is persisting has anything to do with a physical file. Make sure to keep it this way.

Generally, I have my entities in an assembly, then my repositories and domain services in another. The application services live outside of the domain model, but leverage it to do its work. So application services use one or domain services or other application services and domain services can use one or more repositories.

Keeping this in mind, you have two places for the actual deletion logic, and a third place to coordinate them. Here is how it would work if I were doing it. The domain service will leverage the repository for the logical delete from the underlying datasource (as well as a retrieval which you will need, as well). It is not aware of anything else other than working with the domain object instance. I also would have an application service (outside of the domain) which specifically dealt with removing the physical instance. For argument sake, I will assume you have an "ImageRepository" class and an "ImageServices" class, which contain your domain repository and your domain services, respectively. Your ImageServices needs a Delete() method, as well as whatever Find() methods you are using. I usually explicitly call the find methods as FindBy...() (i.e, FindByKey(), FindByName(), etc.).

You don't want to remove the logical instance if you haven't been able to remove the physical instance, so make sure you have a means of measuring success of the removal operation for the physical image. I would probably go with some sort of a custom exception in this case (since I would consider deleting a file to be a standard operation that should not commonly fail). This usually falls in the realm of "management". So usually I have an application service named something like "ImageManagementService". For simplicity sake, this service (since it is part of the application and not the domain) can have a private method to do the physical delete. Let's call it "DeleteImageFile()".

The third place is a coordination of these two operations, also as an application service. I would just make this the public method in the "ImageManagementService". We can call this one "RemoveImage". This application service will do the following:

  1. Retrieve the instance information from the domain services (a passthrough call to your repository).
  2. Use the instance information to locate the physical file and remove it (the first application service mentioned, again).
  3. If the physical removal is successful, delete the instance (back to the domain service, facading the repository again).

So, what happens is the application itself calls the "RemoveImage()" method from the "ImageManagementService" instance. Internally, "RemoveImage()" first calls the "FindBy..()" from the domain's "ImageServices" to get an instance from the domain. The filepath is used from there to call to the private "DeleteImageFile() method in the "ImageManagementService" instance. Upon success, it will then call the "Delete()" methods in the domain's "ImageService", which is acting as a facade to your repository.

I think it is very important to focus on the separation of concerns in this case, because if you have an explicit separation (which you can do with different assemblies) you will become comfortable with knowing which kind of logic can go in which place. I highly recommend the Evan's book. Also, for a quick hit on the SOC concept as it relates to DDD, I recommend taking a look at Jeffrey Palermo's three part series on the "Onion Architecture".

Just a couple of notes as to why you would use a domain service instead of calling the repository directly from the application service. Primarily, the repository has more complicated instancing then the domain service. Remember, it is mostly a facade, but might have additional logic that does not fit in anywhere else in the domain. A good example of this might be if you wanted to enforce a unique filename. The domain object itself has no knowledge of other domain objects in other aggregates directly, so the domain service might check for an existing instance with the same name prior to a save operation. Very handy, indeed! Also, a domain service is not limited to a single repository. You can have a domain service coordinate efforts between multiple repositories. If you have overlapping aggregates, you might need to call work with two related aggregate roots at the same time. you can do this in the domain service, keeping that sort of logic in the domain and not bleeding into the application.

Hope this helps. I am sure that there are other ways to do this, but this is the way that I have found success in my own applications with similar scenarios.

joseph.ferris
+1  A: 

@joseph.ferris: "Generally, I have my entities in an assembly, then my repositories and domain services in another. "

Personally, I prefer to see assemblies as a unit of deployment, not a separation of concerns design tool. For that, I'd rather use namespaces.

Ensuring no cyclic-dependencies (between those namespaces) that way is harder, but tools like NDepend can help out.

Martin R-L
I actually use it for both. The fact that they are separated in this fashion allow for us to distribute the assembly containing the entities to 3rd parties who consume our web services, for example, without having access to other concerns. It is also about frequency of change and the "ripple effect". Repositories and services have a higher rate of change than the entities themselves. Obviously, you don't want to be too fragmented, but you also don't want to end up having everything in one assembly, either.
joseph.ferris
Additionally, we use the "assembly per layer" approach in the domain. There are only two real assemblies in this case, though, as mentioned in the previous comment. Through code review, we enforce that you can only call down the stack and not up it. So, domain services use repositories and entities. Repositories can use entities. It is fairly simple to determine if the entities vs. respository/services relationship is violated, since it would have to be referenced in the project. Removing the reference, if it exists, instantly shows all places that this have been violated.
joseph.ferris
A: 

On a first approach, I think I would opt for the most simple approach, and delete the physical image from disk inside the ImageRepository. It is maybe not the most 'correct' or 'pure' solution, but it is the most simple one, and this conforms to the 'choose the most simple solution that works' adagio.

When, in a later phase of the project, you feel that this solution is not good, and you feel you need a more complex (and maybe more pure) solution like the one proposed by joseph.ferris, then you can always refactor it.

It is easier to refactor a simple solution, then to refactor a complex solution. :)

Frederik Gheysels
You have to be careful, though, because it is a really fine line. In this case you are basically writing about the same amount of code, except what you have in one method if you put it in the repository, needs about nine additional lines, in the form of method declarations and braces. It is different for every team, but my team will go with the simple solution as long as the solution doesn't take on concerns of the other layer. For a simplistic approach, I could still see rolling it into one method, but I would do it in the application layer, calling the repository's delete method after.
joseph.ferris