What are the different types of 1:M work order objects? - maximo

In Maximo 7.6.1.1, there appear to be several different kinds of 1:M work order objects.
These objects can be used to manage WOs that pertain to multiple assets or WOs that pertain to multiple tasks.
What would be an exhaustive list of all the different 1:M object types that are related to WOs?
Here's what I've come up with so far:
Parent WO --> Child WOs
WO --> Multi assets, locations, CIs
WO --> Meter readings (that generate WOs)
WO --> Tasks
WO --> Activities
WO --> Inspections
Other (technically not related to WOs):
Collections**
Saved queries** (assets & locations)
Routes (generates child WOs, multi-assets/locations/CIs, or routes)
** Can be used to generate 1:M records via an automation script.
Are there any others that are missing from that list?

Look under Database Configuration > Object WORKORDER > Relationships tab
If you look at the Relationship name, you will see the where clause Maximo will use to join the two objects together. That will give you some insight into which relationships you may be able to leverage.
Remember to look at the indexes and form your query to utilize indexes so your joins execute quickly. Any database client with an EXPLAIN PLAN feature should tell you if your query is executing quickly and if not, what is causing the delay (to an extent).

Related

How to ensure data consistency between two different aggregates in an event-driven architecture?

I will try to keep this as generic as possible using the “order” and “product” example, to try and help others that come across this question.
The Structure:
In the application we have 3 different services, 2 services that follow the event sourcing pattern and one that is designed for read only having the separation between our read and write views:
- Order service (write)
- Product service (write)
- Order details service (Read)
The Background:
We are currently storing the relationship between the order and product in only one of the write services, for example within order we have a property called ‘productItems’ which contains a list of the aggregate Ids from Product for the products that have been added to the order. Each product added to an order is emitted onto Kafka where the read service will update the view and form the relationships between the data.
 
The Problem:
As we pull back by aggregate Id for the order and the product to update them, if a product was to be deleted, there is no way to disassociate the product from the order on the write side.
 
This in turn means we have inconsistency, that the order holds a reference to a product that no longer exists within the product service.
The Ideas:
Master the relationship on both sides, which means when the product is deleted, we can look at the associated orders and trigger an update to remove from each order (this would cause duplication of reference).
Create another view of the data that shows the relationships and use a saga to do a clean-up. When a delete is triggered, it will look up the view database, see the relationships within the data and then trigger an update for each of the orders that have the product associated.
Does it really matter having the inconsistencies if the Product details service shows the correct information? Because the view database will consume the product deleted event, it will be able to safely remove the relationship that means clients will be able to get the correct view of the data even if the write models appear inconsistent. Based on the order of the events, the state will always appear correct in the read view.
Another thought: as the aggregate Id is deleted, it should never be reused which means when we have checks on the aggregate such as: “is this product in the order already?” will never trigger as the aggregate Id will never be repurposed meaning the inconsistency should not cause an issue when running commands in the future.
Sorry for the long read, but these are all the ideas we have thought of so far, and I am keen to gain some insight from the community, to make sure we are on the right track or if there is another approach to consider.
 
Thank you in advance for your help.
Event sourcing suites very well human and specifically human-paced processes. It helps a lot to imagine that every event in an event-sourced system is delivered by some clerk printed on a sheet of paper. Than it will be much easier to figure out the suitable solution.
What's the purpose of an order? So that your back-office personnel would secure the necessary units at a warehouse, then customer would do a payment and you start shipping process.
So, I guess, after an order is placed, some back-office system can process it and confirm that it can be taken into work and invoicing. Or it can return the order with remarks that this and that line are no longer available, so that a customer could agree to the reduced order or pick other options.
Another option is, since the probability of a customer ordering a discontinued item is low, just not do this check. But if at the shipping it still occurs - then issue a refund and some coupon for inconvenience. Why is it low? Because the goods are added from an online catalogue, which reflects the current state. The availability check can be done on the 'Submit' button click. So, an inconsistency may occur if an item is discontinued the same minute (or second) the order has been submitted. And usually the actual decision to discontinue is made up well before the information was updated in the Product service due to some external reasons.
Hence, I suggest to use eventual consistency. Since an event-sourced entity should only be responsible for its own consistency and not try to fulfil someone else's responsibility.

How to choose the proper bounds for an Aggregate in DDD?

I'm trying to follow the DDD (Domain-Driven Design) approach in my project. My domain is a barbershop and the use case that I want to implement is - book an appointment.
The problem that I have is how to correctly organize boundaries around my aggregate that should help to handle this use cases (book an appointment).
Let's say I have a barber. Barber has working days. Working day has working hours (e.g. 09:00 - 20:00), breaks, and other booked appointments.
Schematically it would look like this
Barber
- WorkingDay
- 09:00-20:00 <- working hours
- Breaks
- 13:00-14:00
- 18:00-19:00
- Appointments
- 09:00-10:00
- 12:00-13:00
- WorkingDay
...
- WorkingDay
...
Rules to be considered:
New appointment must not overlap existing breaks
New appointment must not overlap existing appointments
New appointment must be within working hours
Working day must exist
Working day must not be in the past
I have two ideas of how to implement this:
Create WorkingDay aggregate which will contain all related breaks and appointments.
Pros:
All rules can be satisfied within WorkingDay aggregate
Single WorkingDayRepository repository
Cons:
Possibly large aggregate*
Create WorkingDay, Break, Appointment aggregates and verify rules in domain services
Pros:
Small aggreages
Cons
Multiple repositories (e.g. WorkingDayRepository, BreakRepository, AppointmentRepository)
Business logic is split between aggregates/domain-services
What other option can be used? Or what approach to follow in my case?
You could model this by using the concept of a Schedule. Schedule is your worker's workdays (or calendar) with appointments. It could be a decent aggregate - schedule has to be always valid.
Then you could have AddAppointment method on the Schedule which will check the invariants.
If you need to track changes, I'd suggest using Domain Events that could be fired and then logged.
To avoid concurrency problems with bookings, i.e. when 2 users are trying to book the same slot, you can:
Organise the DB in the way that it will not be possible to double-book, ie in your bookings table you will have a unique constraint on <time slot id>, <barber id> and when the second user is trying to book (insert a booking into the table) they will fail with unique constraint violation. Not sure how your db is structured but hope you get the idea.
use DB transactions. Both will try and update the row with time slot, only one should succeed.
put booking requests for the same barber into a queue.

Breaking 2D Hierarchical Data into a relational database

I have the output of SAP that looks like the following:
The ObjectName is a field that represents the level that the line is attributed too. The order in this case is important as all those level 5 items belong to level 4 which belong to level 3 etc. I know the manual solution is easy but what I really need to do is link the items together in a tree structure. Ideally a code that the first instance of level 0 = 1 the second is 2 etc. level 1 becomes 1.1, 1.2, 1.3, etc. Level 3 1.1.1, 1.1.2, 1.1.3. etc.
This also needs to be repeatable in either DAX or VBA as I would like to build a report with the data regularly, I am a little stumped with the logic behind it.
I am mainly trying to drive some logic to define the relationships in the data lines. The issue is that each line contains level data but the only link between child and parent is the location in the spreadsheet, children are under the parents. In a tree structure as the spreadsheet is about 100k lines long, this becomes a laborious task to do manually.
Any directions to look into would be great.
You would need to have two columns - one that would act as a key and another that would point to the parent key. Then you can use DAX functions for handling parent-child hierarchies (PATH, PATHITEM, etc.).
Look here for here for an example: https://www.daxpatterns.com/parent-child-hierarchies/

CQRS Read Model Projections: How complex is too complex a data transformation

I want to sanity check myself on a view projection, in regards to if an intermediary concept can purely exist in the read model while providing a bridge between commands.
Let me use a contrived example to explain.
We place an order which raises an OrderPlaced event. The workflow then involves generating a picking slip, which is used to prepare a shipment.
A picking slip can be generated from an order (or group of orders) without any additional information being supplied from any external source or user. Is it acceptable then that the picking slip can be represented purely as a read model?
So:
PlaceOrderCommand -> OrderPlacedEvent
OrderPlacedEvent -> PickingSlipView
The warehouse manager can then view a picking slip, select the lines they would like to ship, and then perform a PrepareShipment command. A ShipmentPrepared event will then update the original order, and remove the relevant lines from the PickingSlipView.
I know it's a toy example, but I have a conceptually similar use case where a colleague believes the PickingSlip should be a domain entity/aggregate in its own right, as it's conceptually different to order. So you have PlaceOrder, GeneratePickingSlip, and PrepareShipment commands.
The GeneratePickingSlip command however simply takes an order number (identifier), transforms the order data into a picking slip entity, and persists the entity. You can't modify or remove a picking slip or perform any action on it, apart from using it to prepare a shipment.
This feels like introducing unnecessary overhead on the write model, for what is ultimately just a transformation of existing information to enable another command.
So (and without delving deeply into the problem space of warehouses and shipping)...
Is what I'm proposing a legitimate use case for a read model?
Acting as an intermediary between two commands, via transformation of some data into a different view. Or, as my colleague proposes, should every concept be represented in the write model in all cases?
I feel my approach is simpler, and avoiding unneeded complexity, but I'm new to CQRS and so perhaps missing something.
Edit - Alternative Example
Providing another example to explore:
We have a book of record for categories, where each record is information about products and their location. The book of record is populated by an external system, and contains SKU numbers, mapped to available locations:
Book of Record (Electronics)
SKU# Location1 Location2 Location3 ... Location 10
XXXX Introduce Remove Introduce ... N/A
YYYY N/A Introduce Introduce ... Remove
Each book of record is an entity, and each line is a value object.
The book of record is used to generate different Tasks (which are grouped in a TaskPlan to be assigned to a person). The plan may only cover a subset of locations.
There are different types of Tasks: One TaskPlan is for the individual who is on a location to add or remove stock from shelves. Call this an AllocateStock task. Another type of Task exists for a regional supervisor managing multiple locations, to check that shelving is properly following store guidelines, say CheckDisplay task. For allocating stock, we are interested in both introduced and removed SKUs. For checking the displays, we're only interested in newly Introduced SKUs, etc.
We are exploring two options:
Option 1
The person creating the tasks has a View (read model) that allows them to select Book of Records. Say they select Electronics and Fashion. They then select one or more locations. They could then submit a command like:
GenerateCheckDisplayTasks(TaskPlanId, List<BookOfRecordId>, List<Locations>)
The commands would then orchestrate going through the records, filtering out locations we don't need, processing only the 'Introduced' items, and creating the corresponding CheckDisplayTasks for each SKU in the TaskPlan.
Option 2
The other option is to shift the filtering to the read model before generating the tasks.
When a book of record is added a view model for each type of task is maintained. The data might be transposed, and would only include relevant info. ie. the CheckDisplayScopeView might project the book of record to:
Category SKU Location
Electronics (BookOfRecordId) XXXX Location1
Electronics (BookOfRecordId) XXXX Location3
Electronics (BookOfRecordId) YYYY Location2
Electronics (BookOfRecordId) YYYY Location3
Fashion (BookOfRecordId) ... ... etc
When generating tasks, the view enables the user to select the category and locations they want to generate the tasks for. Perhaps they select the Electronics category and Location 1 and 3.
The command is now:
GenerateCheckDisplayTasks(TaskPlanId, List<BookOfRecordId, SKU, Location>)
Where the command now no longer is responsible for the logic needed to filter out the locations, the Removed and N/A items, etc.
So the command for the first option just submits the ID of the entity that is being converted to tasks, along with the filter options, and does all the work internally, likely utilizing domain services.
The second option offloads the filtering aspect to the view model, and now the command submits values that will generate the tasks.
Note: In terms of the guidance that Aggregates shouldn't appear out of thin air, the Task Plan aggregate will create the Tasks.
I'm trying to determine if option 2 is pushing too much responsibility onto the read model, or whether this filtering behavior is more applicable there.
Sorry, I attempted to use the PickingSlip example as I thought it would be a more recognizable problem space, but realize now that there are connotations that go along with the concept that may have muddied the waters.
The answer to your question, in my opinion, very much depends on how you design your domain, not how you implement CQRS. The way you present it, it seems that all these operations and aggregates are in the same Bounded Context but at first glance, I would think that there are 3 (naming is difficult!):
Order Management or Sales, where orders are placed
Warehouse Operations, where goods are packaged to be shipped
Shipments, where packages are put in trucks and leave
When an Order is Placed in Order Management, Warehouse reacts and starts the Packaging workflow. At this point, Warehouse should have all the data required to perform its logic, without needing the Order anymore.
The warehouse manager can then view a picking slip, select the lines they would like to ship, and then perform a PrepareShipment command.
To me, this clearly indicates the need for an aggregate that will ensure the invariants are respected. You cannot select items not present in the picking slip, you cannot select more items than the quantities specified, you cannot select items that have already been packaged in a previous package and so on.
A ShipmentPrepared event will then update the original order, and remove the relevant lines from the PickingSlipView.
I don't understand why you would modify the original order. Also, removing lines from a view is not a safe operation per se. You want to guarantee that concurrency doesn't cause a single item to be placed in multiple packages, for example. You guarantee that using an aggregate that contains all the items, generates the packaging instructions, and marks the items of each package safely and transactionally.
Acting as an intermediary between two commands
Aggregates execute the commands, they are not in between.
Viewing it from another angle, an indication that you need that aggregate is that the PrepareShippingCommand needs to create an aggregate (Shipping), and according to Udi Dahan, you should not create aggregate roots (out of thin air). Instead, other aggregate roots create them. So, it seems fair to say that there needs to be some aggregate, which ensures that the policies to create shippings are applied.
As a final note, domain design is difficult and you need to know the domain very well, so it is very likely that my proposed solution is not correct, but I hope the considerations I made on each step are helpful to you to come up with the right solution.
UPDATE after question update
I read a couple of times the updated question and updated several times my answer, but ended up every time with answers very specific to your example again and I'm most likely missing a lot of details to actually be helpful (I'd be happy to discuss it on another channel though). Therefore, I want to go back to the first sentence of your question to add an important comment that I missed:
an intermediary concept can purely exist in the read model, while providing a bridge between commands.
In my opinion, read models are disposable. They are not a single source of truth. They are a representation of the data to easily fulfil the current query needs. When these query needs change, old read models are deleted and new ones are created based on the data from the write models.
So, only based on this, I would recommend to not prepare a read model to facilitate your commands operations.
I think that your solution is here:
When a book of record is added a view model for each type of task is maintained. The data might be transposed, and would only include relevant info.
If I understand it correctly, what you should do here is not create view model, but create an Aggregate (or multiple). Then this aggregate can receive the commands, apply the business rules and mutate the state. So, instead of having a domain service reading data from "clever" read models and putting it all together, you have an aggregate which encapsulates the data it needs and the business logic.
I hope it makes sense. It's a broad topic and we could talk about it for hours probably.

How to model a large catalogue with many different types of products and attributes in PimCore 5+

I am an experienced full-stack web dev, but new to PimCore. I’m organising a large catalogue of many types of items in PimCore and have looked through the documentation many times, but I still don’t know how to tackle two basic issues I have organising my product data into classes. I hope some more experienced PimCore users or devs can shine some light on this.
Issue 1: how to model general product attributes that apply to all products in the catalogue.
All of the products in my catalogue will have a name and a description, so I thought it made sense to make a Product class that has these fields and make all of my specific product classes subclasses of that Product class so I wouldn’t have to add name and description fields to each subclass individually.
I tried to set this up, but in the object editor of the specific subclass the layout fields that I added to the generic Product superclass don’t show up. Am I missing something here? Should my approach actually work? If not, what would be the PimCore approach to modelling this?
Issue 2: how best to model products with multiple options, ie. variants over more than one dimension.
For example, T-shirts with both color and size options (let’s say, 3 colors and 3 sizes for a total of 9 variants). I would want to create one single T-shirt product in the object tree and then add 3 color options and 3 size options for an (automatic) total of 9 variants. I want the T-shirt to appear as a single product in the e-commerce frontend and let the end customer determine the value of both options.
I am wondering if it is at all possible to do this in a way that allows me to specify the 3 color options and the 3 sizes independently of each other. The examples I find in the documentation all show me a fully expanded object tree covering all options (eg., 1 T-shirt object, with 3 subobjects for each size, each with 3 subobjects for each color in that size). Although data inheritance helps with the management of this info, a change in the available colors would still have to be made once for every size option. I can’t imagine there is not a better way to set up object variants in multiple dimensions in PimCore, but days of searching have led me nowhere. Am I missing something here? Or does PimCore actually force you to create objects/variants for every combination of product options? If not, what would be the PimCore approach to modelling this?
I hope someone with a little experience in this field is willing to shed some light on these two issues. Thank you so much!!
Received very helpful answers on the PimCore forums, by user fash:
Issue 1: Pimcore DataObject classes cannot inherit from each other. The way to go would be to create one product class (that
contains all common product attributes) and then use object bricks or
classification store groups to model category specific attributes.
Then on object level, the corresponding object brick or classification
store group can be added to the product object (depending on its
category or other criteria).
Issue 2: As you already noticed, the default way of dealing with different variants of a product is to create an object instance for
each variant and utilize data inheritance to reduce data maintenance
effort (like in the demo). Also as Andrew already pointed out, adding
some helper functionality like a generate variants button is easily
possible.
The reason why we create a unique data object for each variant most of
the time is, that normally each SKU has a unique product number and
also in terms of e-commerce it needs to be possible to reference the
exact variant that was ordered. As an alternative you of course could
use data structures like field definitions or block to follow your
approach and have to attributes (like color, size, etc.) and add
multiple values to them and then deal on the output channel with the
variant generation. It is really up to your use case and your system
what fits better.
An hybrid solution would be to define possible variants with variant
attributes, and then generate the actual object variants on the fly
when one is ordered.

Resources