5 Replies Latest reply on Jul 15, 2009 10:14 PM by varunmehta

    Some objects, still want to link up with older versions

    varunmehta

      Usecase;
      For sake of simplicity I've tried to use a Restaurant-Menu-Order analogy.

      You have a Menu for a restaurant. Since it's a high demand restaurant, they need you to reserve your table 3-6 months in advance, along with your dinner order. So when you reserve, they tell you the table number and the amount you'll have to pay on that day.

      We have 3 objects in the system;

      Order [1--*] OrderLineitem
      OrderLineitem [*--1] MenuItem
      
      [1--*]: OneToMany
      [*--1]: ManyToOne


      An Order contains a group of OrderLineitems, and a lineitem is linked with MenuItem details along with some other information.

      The user booked the Order, with multiple MenuItems. During the 6 months (after the order was placed) if the price of the MenuItem changed, the person should still be billed at the same old price (honor the price), and any new orders coming into the restaurant henceforth after the price change, will be charged the new price. (for sake of simplicity we have just considered price, but add some 10-15 more properties to it)

      As on 1 Jan 2009 (placing the order)
      --------------------
      
      MenuItems: (all are version 1 - v1)
      Pasta, $10 (v1)
      Meatballs, $20 (v1)
      Burrito, $7 (v1)
      Vanilla Icecream $5 (v1)
      
      Order:
      Table No, 15;
      Date 1 Jan 2009
       OrderLineitems:
       - Pasta, 2, (v1)
       - Burrito, 1, (v1)


      The user arrives 6 months later on 1 June 2009 to eat (finally!), but along the way, the prices of certain MenuItems have been changed more than once.

      As on 1 June 2009 (enjoying the dinner)
      --------------------
      
      MenuItems:
      Pasta, $15 (v3)
      Meatballs, $20 (v1)
      Burrito, $7 (v1)
      Vanilla Icecream $10 (v4)
      
      Order:
      Table No, 15;
      Date 1 Jan 2009
       OrderLineitems:
       - Pasta, 2, (v1)
       - Burrito, 1, (v1)
       - Ice cream, 2, (v4) --> they added this on the last minute


      So for Pasta and Burrito they'll be charged the old rate and for the ice-cream the new price.

      Now to make things more fun. There were a 100 orders in between and over 50 price changes during that period and depending on the version of the MenuItem when the order was placed, the user needs to be billed as per that price.

      ENVERS, always links with the latest price (object) changes in the system. All the old prices are always available (in _aud tables), but are fetched only when asked for.

      Using the hibernate ManyToOne mapping, when an OrderLineitem fetches a MenuItem, it always fetches the latest MenuItem object, but rather should fetch as per that version. We can lazy loading the object, so should be able to do it, without having to add to much extra code.

      Using the Temporal Object terminology (Martin Fowler),

      MenuItem is a "Temporal Object" with "Temporal Properties". An OrderLineitem wants to use a "Snapshot" of the MenuItem at any given point of time.

      The current envers implementation allows only the current time "Snapshot" of the object.

      Hopefully this was simple to understand. If wanted I can put up the code base for this.

      Any suggestions on how to approach this problem using Envers;



        • 1. Re: Some objects, still want to link up with older versions
          varunmehta

          Adding to the point, if there a suggestion on how to solve this issue and other people need it, I'll be happy to provide a patch.

          • 2. Re: Some objects, still want to link up with older versions
            adamw

            Hello,

            I don't think I quite understand.

            Envers doesn't link to the latest price (object) in the system. If you read an Order at revision corresponding to 1st january (using AuditReader), and then get the related OrderLineItems/MenuItems, they will also be read at the same revision (corresponding to 1st january). So you should get what you want.

            Adam

            • 3. Re: Some objects, still want to link up with older versions
              varunmehta

              Here is the POJO sample; for all the three objects.

              Order:

              @Entity
              @Table(name = "`order`")
              @Audited
              public class Order extends AbstractEntity {
              
               private int orderId;
               private int tableNumber;
               private Date startDate;
               private Date endDate;
               private String comments;
               private List<OrderLineitem> lineitems;
              
               @Id
               @GeneratedValue(strategy = GenerationType.AUTO)
               public int getOrderId() {
               return orderId;
               }
              
               // other getters/setters
              
               @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
               @Cascade(value = { org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
               @AuditJoinTable
               @Audited
               public List<OrderLineitem> getLineitems() {
               return lineitems;
               }
              
               public void setLineitems(List<OrderLineitem> lineitems) {
               this.lineitems = lineitems;
               }
              }


              OrderLineitem:
              @Entity
              @Audited
              public class OrderLineitem extends AbstractEntity {
              
               private int orderLineitemId;
               private int quantity;
               private MenuItem menuItem;
               private Order order;
              
               @Id
               @GeneratedValue(strategy = GenerationType.AUTO)
               public int getOrderLineitemId() {
               return orderLineitemId;
               }
              
               public void setOrderLineitemId(int orderLineitemId) {
               this.orderLineitemId = orderLineitemId;
               }
              
               public int getQuantity() {
               return quantity;
               }
              
               public void setQuantity(int quantity) {
               this.quantity = quantity;
               }
              
               @ManyToOne
               @JoinColumn(name = "order_id", nullable = false, updatable = false)
               @AuditJoinTable
               public Order getOrder() {
               return order;
               }
              
               public void setOrder(Order order) {
               this.order = order;
               }
              
               @ManyToOne
               @JoinColumn(name = "menu_id")
               @AuditJoinTable
               public MenuItem getMenuItem() {
               return menuItem;
               }
              
               public void setMenuItem(MenuItem menuItem) {
               this.menuItem = menuItem;
               }
              
              }
              


              And finally the MenuItem:
              @Entity
              public class MenuItem extends AbstractEntity {
              
               public enum Status {
               ACTIVE, INACTIVE;
               }
              
               private int menuItemId;
               private BigDecimal price;
               private Status status;
              
               @Id
               @GeneratedValue(strategy = GenerationType.AUTO)
               public int getMenuItemId() {
               return menuItemId;
               }
              
               public void setMenuItemId(int menuItemId) {
               this.menuItemId = menuItemId;
               }
              
               @Audited
               public BigDecimal getPrice() {
               return price;
               }
              
               public void setPrice(BigDecimal price) {
               this.price = price;
               }
              
               @Enumerated(EnumType.STRING)
               @Column(columnDefinition = "enum('ACTIVE', 'INACTIVE')")
               public Status getStatus() {
               return status;
               }
              
               public void setStatus(Status status) {
               this.status = status;
               }
              }


              Now in this typical case, if I loaded the OrderLineItem object, and then wanted the MenuItem object using the code below, it'll always load the latest MenuItem and not the version, this OrderLineitem should be referring to.
              orderLineitem.getMenuItem()


              Or should I write an internal wrapper which calls the AuditReader everytime and then fetches the object.

              Please suggest. I'm a bit lost and confused at this point.

              • 4. Re: Some objects, still want to link up with older versions
                adamw

                Hello,

                if you load the the object using an audit reader (AuditReader interface) at a revision in the past, then when you'll call orderLineitem.getMenuItem(), you will get the menu item at that revision. It won't always load the latest MenuItem.

                By the way, why do you have @AuditJoinTable at @ManyToOne annotated fields?

                Adam

                • 5. Re: Some objects, still want to link up with older versions
                  varunmehta

                   

                  "adamw" wrote:
                  Hello,

                  if you load the the object using an audit reader (AuditReader interface) at a revision in the past, then when you'll call orderLineitem.getMenuItem(), you will get the menu item at that revision. It won't always load the latest MenuItem.

                  By the way, why do you have @AuditJoinTable at @ManyToOne annotated fields?

                  Adam


                  Ahh, so I load the Order (cos I want the current revision of it), then need to get the AuditReader, find it's (order's) latest revision and using that revision as reference load the MenuItem, which will then be load that version of the MenuItem it was saved at.

                  Tried it got it working... Thanks