Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring: decouple DI from businessObject #1473

Merged
merged 13 commits into from
Sep 3, 2021
Merged

Conversation

marstamm
Copy link
Contributor

@marstamm marstamm commented Aug 5, 2021

closes #1472

@bpmn-io-tasks bpmn-io-tasks bot added the in progress Currently worked on label Aug 5, 2021
@marstamm marstamm force-pushed the 1472-bo-di-decoupling branch 4 times, most recently from fe3bc35 to d36f05a Compare August 6, 2021 09:10
@marstamm marstamm changed the title chore: decouple di from business object Refactoring: decouple DI from businessObject Aug 6, 2021
@marstamm marstamm marked this pull request as ready for review August 6, 2021 09:22
@bpmn-io-tasks bpmn-io-tasks bot added needs review Review pending and removed in progress Currently worked on labels Aug 6, 2021
@marstamm marstamm requested review from a team, philippfromme and pinussilvestrus and removed request for a team August 6, 2021 09:22
@marstamm
Copy link
Contributor Author

marstamm commented Aug 9, 2021

I overlooked some issues in the create/Replace behavior of Collapsible Shapes and will fix it

EDIT: done

@marstamm marstamm force-pushed the 1472-bo-di-decoupling branch from 39f62b7 to 46d3422 Compare August 9, 2021 10:10
lib/util/ModelUtil.js Outdated Show resolved Hide resolved
Copy link
Member

@nikku nikku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great change overall.

I like in particular how straight forward this is and that you introduced a utility to use consistently to retrieve the DI. 💪

@marstamm marstamm force-pushed the 1472-bo-di-decoupling branch from 46d3422 to 12818ce Compare August 20, 2021 07:18
@nikku
Copy link
Member

nikku commented Aug 20, 2021

As this is a breaking change extensions may rely on, one last question before we merge this: What is our story for backwards compatibility / allowing users to gracefully update?

I could imagine two things:

  • Clearly pharsed BREAKING CHANGES in the Changelog (a must!)
  • Throwing a meaningful error upon accessing businessObject.di (optional)

@marstamm marstamm force-pushed the 1472-bo-di-decoupling branch from 67776a3 to 3a77942 Compare August 25, 2021 06:53
@marstamm
Copy link
Contributor Author

I added the breaking changes to the changelog.

For throwing an error: where would be a good place to start looking into it? Ideally, we would have a Accessor on the business Object. As far as I can tell, they are created in bpmn-moddle and we would have to modify our code everywhere in bpmn-js where new ones are created (import, new elements, replace(?), ...).

@nikku
Copy link
Member

nikku commented Aug 25, 2021

We did establish the relationship here and could make that a property that throws.

Object.definedProperty(el, 'di', {
  get: function() {
    throw 'HELLO YOU';
  }
});

My personal opinion is: It does make sense to be verbose about it.

@marstamm
Copy link
Contributor Author

Thanks for the hint! I decided to create the error in the elementFactory. This way, we also cover newly created elements and not only the imported ones.

Good thing we did it, because I also found 2 places where we still accessed the di incorrectly.

@marstamm
Copy link
Contributor Author

I hope I selected the correct files to add the tests to 🤞

I kept the previous commit to make it clear what has changed, the commit history should probably be cleaned up anyway before we merge it.

@nikku
Copy link
Member

nikku commented Aug 25, 2021

Thanks for your continued follow-ups. The last thing I'm wondering: How could the tests not detect the DI miss-use?

I'll look into your last changes tomorrow and see if we're merge-ready.

@marstamm
Copy link
Contributor Author

Regarding the tests: we used the (wrong) di to check if it is part of the diPlane. When we accessed it incorrectly, connectionDi was undefined and the test green.

Should we also assert that connectionDi exists in the test cases? Here is the line where connectionDi is used:

expect(subProcessDi.$parent.get('planeElement')).not.to.include(connectionDi);

@nikku
Copy link
Member

nikku commented Aug 26, 2021

Let us do that, to verify tests do not accidentally pass again.

We can do so in a assume block.

// given
...

// assume
expect(connectionDi).to.exist;

// when
...

It is a pattern that we use in our code base.

@nikku nikku force-pushed the 1472-bo-di-decoupling branch 2 times, most recently from 92548e9 to d3fc471 Compare August 27, 2021 15:57
In the diagram `di` is now accessed via the diagram element, not the
business object. This has the benefit that elements in multiple diagrams
can easily be represented.

Related to #1472

BREAKING CHANGE:

* Instead of referencing the `di` from the business object, reference it
  from the diagram element representing it.
@nikku nikku force-pushed the 1472-bo-di-decoupling branch from 46cb456 to 77f872c Compare August 27, 2021 19:13
@@ -405,6 +410,11 @@ BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {
return;
}

// Cover the case where di.$parent === undefined and parentDi === null
if (!parentDi && !di.$parent) {
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When does this case occur and do we have a test case for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens when we replace a Task with an expanded sub-process. The newly created Start event has a di.$parent = null.

We don't test this explicitly, but tests like this check if we can replace Tasks with expanded sub-processes

it('should add start event child to subprocess', inject(
function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task_1'),
expandedSubProcess,
startEvents;
// when
expandedSubProcess = bpmnReplace.replaceElement(task, {
type: 'bpmn:SubProcess',
isExpanded: true
});
// then
startEvents = getChildStartEvents(expandedSubProcess);
expect(startEvents).to.have.length(1);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that explaination.

But then, why did it not blow up in the past? I'll investigate and report back.

Copy link
Member

@nikku nikku Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root cause here is that DI updating is now broken on replace and makes that quick fix necessary. I'll continue to investigate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is the issue. We cannot blindly re-use the existing DI on replace but must continue to create a new one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise the following will happen:

  • Task (to be replaced) gets removed
  • DI (of task and replaced element) is being unlinked
  • Replaced element does not have a proper DI reference anymore

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for proper DI updating after replace (BpmnReplaceSpec) are missing entirely and need to be added.

Copy link
Member

@nikku nikku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've cleaned up the commit history on this one, added additional test coverage for label wiring (was non-existing in a few places) and found two other small gotchas that I've cleaned up.

At this point, there is one last comment from my side remaining.

Looks great overall. We should proceed and get this out of the door.

Copy link
Member

@nikku nikku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found the last bug ™️.

Please have a look. We're light on DI update test coverage, this is why this one did not blow up on your PR.

@marstamm
Copy link
Contributor Author

marstamm commented Aug 30, 2021

Found the root-cause:

When creating elements in the elementFactory, we supported passing an Object with di Attributes. We now expect the di to be present or absent.
attrs.di is now used for the di moddleElement, so I broke the option to pass an Object.

We should probably fix this, but I'm not sure what's the best way:

  1. Overload attrs.di and check if attrs.di is an Object or a Moddle element. Do we have utility functions to check for a Moddle element?
  2. have a separate attrs.diProperties to be applied to the newly created di
  3. Require the di to be created before createBpmnElement is called

2 and 3 would be a change to the API, but feel cleaner than overloading the attribute

@nikku
Copy link
Member

nikku commented Aug 31, 2021

It is safe to use ModelUtil#is on diagram elements and plain objects. I'd therefor advocate for option 1️⃣.

See also BpmnFactory where we use the utility to verify if an element needs an ID.

@nikku
Copy link
Member

nikku commented Aug 31, 2021

Also, we do a similar instanceof if not create check in our core Modeling API already.

expect(createdEvent).to.eql(startEvent);
expect(createdEvent).to.exist;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BpmnReplace will now always create a new di and shape. This is the only place where we checked if it's the same, all other tests just test for an existing new shape as well:

inject(function(elementRegistry, modeling, elementFactory) {
// given
var subProcess = elementRegistry.get('SubProcess_3'),
startEvent = elementFactory.createShape(
{ type: 'bpmn:StartEvent', eventDefinitionType: 'bpmn:TimerEventDefinition' }),
id = startEvent.businessObject.id;
// when
modeling.createElements(
startEvent, { x: subProcess.x + 30, y: subProcess.y + 30 }, subProcess);
// then
var createdEvent = elementRegistry.get(id);
expect(createdEvent).to.exist;
expect(createdEvent.businessObject.eventDefinitions).to.not.exist;
})

@marstamm marstamm force-pushed the 1472-bo-di-decoupling branch from fc2201a to 47d1fa0 Compare August 31, 2021 14:00
This fixes the existing DI creation methods in `BpmnFactory` and
simplifies the related `ElementFactory` code that relied on it.

In the past args got ignored and passing attrs to the created DI
was not possible, now it is.

BREAKING CHANGE:

With this change the following `BpmnFactory` API methods got reworked to
take (businessObject, attrs) as an input:

  * `BpmnFactory#createDiEdge`
  * `BpmnFactory#createDiShape`
  * `BpmnFactory#createDiPlane`
Copy link
Member

@nikku nikku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added another minor cleanup on top.

Let us merge this and fix things that are left open later on 🎉.

@nikku nikku merged commit fea7eeb into develop Sep 3, 2021
@nikku nikku deleted the 1472-bo-di-decoupling branch September 3, 2021 13:14
@bpmn-io-tasks bpmn-io-tasks bot removed the needs review Review pending label Sep 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decouple DIs from business Objects
3 participants