Makespace / members-app

Simplifying operations for Cambridge MakeSpace
MIT License
1 stars 1 forks source link

Register training sheet for equipment #21

Closed Lan2u closed 3 weeks ago

Lan2u commented 1 month ago

Functionally: Add the ability to register a training form/id for a piece of equipment.

Actually: Try and workout what is going on.

My biggest question is around 'events'. Is the idea that everything is represented as a history/list of events and everything is worked out from that?

Would you expect to be able to do?:

function unwrap<T>(val: O.Option<T>): T {
    if (O.isSome(val)) {
        return val.value;
    }
    throw new Error('Failed to unwrap');
}

function callCommand<T> (func: Command<T>["process"], command: T) {
    return (events: ReadonlyArray<DomainEvent>) => RA.append(
        unwrap(func({
            command,
            events,
        }))
    )(events);
}

function createEquipment(events: ReadonlyArray<DomainEvent>){
    const areaName = faker.commerce.productName() as NonEmptyString;
    const areaDescription = faker.commerce.productDescription();
    const areaId = v4() as UUID;
    const equipmentId = v4() as UUID;
    return {
        events: pipe(
            events,
            callCommand(create.process, {
                id: areaId,
                name: areaName,
                description: areaDescription,
            }),
            RA.map(e => { console.log(e); return e; }),
            callCommand(add.process, {
                id: equipmentId,
                name: faker.commerce.productName() as NonEmptyString,
                areaId: areaId,
            }),
            RA.map(e => { console.log(e); return e; }),
        ),
        equipmentId,
    };
}

I tried this and it didn't work because add.process uses RA.match which seems to only construct an event if the existing events are empty?

Maybe I misunderstand the point of events / how its supposed to be used?

Lan2u commented 1 month ago

The idea is to follow CQRS principles and separate the concerns of commands from queries. In this case that means:

  • it is the read-models responsibility to deal with e.g. training sheet registrations for equipment that does not exist

Isn't the read-models job just to handle the reading of the current state to workout things? It feels weird that a command to register a training sheet for example wouldn't be the thing to do the checks that the equipment already exists especially as it is already enforcing things like data structure via 'decode' and authorisation.

  • 'resources' are only allowed to care about events that they raise

I like this idea for the simplicity it brings although I expect we will find cases we want to break this rule

  • the 'process' function of a resource may never fail it can either produce an event or not
  • 'process' responsibilities are mostly to ensure idempotency

Ok interesting that makes more sense as to why things are written as they are. Given process is already going to be doing validation why isn't all the validation for if an event can be created put* here? We can then change the return type to be an 'Either' allowing us to easilly hook this up to a UI/frontend and display a user-friendly reason why their command failed.

* obviously using reusable helpers / validation functions so we don't have copy/pasted code everywhere

  • turning the history of events into a meaningful and consistent respresentation for users is a query/read-model responsibility

Yep that makes sense / is intuitive.

The 'unwrap' like functionality is implemented as 'getSomeOrFail' and 'getRightOrFail' for use in tests.

Ah cool, tbh I expected it did already exist somewhere