Need help modeling a relation between images and some classes that use them

Viewed 67

I have an Image aggregate, which has 3 properties: content, filename (as key) and mimetype.

Then I have one Company aggregate with the photos and logo properties.

Those properties hold a filename (the image's key) as value, pointing thus to the Image.

There are some other aggregates using images this way.

The thing is, I need the image to be used just once in the whole system. If I use the image in one company, it cannot be used in another. If I use it as a logo, it cannot be used as a photo, an so on.

All solutions I tried smells a lot, so maybe some of you can bring some ideas!

Thanks!

2 Answers

I think the Eben Roux's commentary is interesting.

In addition, if you work in Domain-Driven Design and depending on the context, you can add a class for each type of photo (using inheritance) or using an ImageType.

I'd suggest introducing the concept of an ImageSlot which simplifies complex AR-image relationships.

Now there's 2 rules you want to be strongly consistent:

  • a) An image slot should not be used more than once.

  • b) An image should not be used more than once.

Solutions:

  1. Inverse relationship and model it on the Image side so that the Image refers to the slot. This ensures to protect a), but you'd need a unique constraint on the slotId to protect b).

  2. Model a bi-directional relationship. With optimistic locking this would ensure TXs using the same images & slots would conflict.

enter image description here

Upon the creation of aggregates that requires you'd also allocate them new slots.

Code for using an image in a given slot at the service layer may look like:

class Service {
    //Assume transactional
    changeImage(slotId, imageId) {
       slot = imageSlotRepository.slotOfId(slotId);
       image = imageRepository.imageOfId(imageId);

       if (image.used()) {
          currentSlot = imageSlotRepository.slotOfId(image.slotId());
          currentSlot.clear();
          image.removeFrom(currentSlot);
       }

       if (!slot.isEmpty()) {
           currentImage = imageRepository.imageOfId(slot.imageId());
           slot.clear();
           currentImage.removeFrom(slot);
       }

       slot.changeImage(image); // detects same slot usage concurrency
       image.useIn(slot); // detects same image usage concurrency
    }
}


class ImageSlot {
    private id;
    private imageId;

    changeImage(newImage) {
        this.imageId = newImage.id();
    }
    clear() {
        this.imageId = null;
    }
    used() { return this.imageId != null }
}

class Image {
    private id;
    private slotId;

    // preconditions ensure the inverse relation has been set
    removeFrom(slot) {
        if (slotId != slot.id) throw wrong slot;
        if (slot.imageId() != null) throw not empty;

        this.slotId = null;
    }
    useIn(slot) {
        if (slot.id() !== this.slotId) throw wrong slot;

        this.slotId = slot.id();
    }
}

DDD is all about those conceptual "breakthroughs". You didin't have the concept of an ImageSlot before and perhaps that's your salvation... or perhaps not ;)

Related