-1

I have a rich client with quite complex domain logic and I want to keep the domain and presentation layer separated. How would you advice to intergrate the domain logic with the UI in this case.

I implemented in the domain objects and services in following fashion

public class ProductOrder
{
     public double SomeParameter {get; private set;}
     public IReadOnlyCollection<ManufactureTime> ManufactureTimeNorms { get; }
     public IReadOnlyCollection<ProductOrderMaterial> MaterialItems { get; }

     public void SetSomeParameter(double value)
     {
           SomeParameter = value;
           RecalculateMaterials(); // modifies domain object
           RecalculateTimeToManufacture();
           RecalculatePrice();
     }
}

Use case

From UX perspective, the workflow is:

  1. User creates product order by selecting a product from calatog.

    • Parameters are copied from catalog to order and based on domain logic material and time needed to manufacture is calculated.
    • User sees the calculated values
  2. User manually changes some parameters

    • Based on domain logic material and time needed to manufacture is calculated.
    • User sees the recalculated values
  3. Manually edits the calculated parameters

  4. User clicks save and the parameters and calculates values are persisted in database.

The problem:

Typically, there is a ViewModels are constructed from a domain objects, presented in the UI where user can make changes and when saving, the domain objects are updated.

However, this does not solve the problem of applying business rules and presenting modified domain object back in the UI. How to design two way interaction between viewmodels and the domain?

Since the business logic is fairly complex, I would like to avoid duplicating all business rules in ViewModel.

What I have:

I don't have anything yet, but I would start with this

public class ProductOrderToViewModelMapper
{
      public void MapToViewModel(ProductOrder domainObject, ProductViewModel viewModel) 
      { ... }

      public void MapToDomainObject(ProductViewModel viewModel, ProductOrder domainObject) 
      { ... }
}
Liero
  • 117
  • 6
  • What is hindering you to update the domain object (`ProductOrder`) when the user changes "some parameters", but without without persisting the updated object? – Doc Brown Aug 30 '23 at 16:30
  • Updating the domain object in realtime is the easy part, but when the domain logic is executed, how do I apply the changes back to viewmodel, sso that "user sees the calculates values" ? Currently I tend to bind presentation layer directly to domain object where possible, but I want to avoid it for obvious reasons – Liero Aug 30 '23 at 20:21
  • 2
    "when the domain logic is executed, how do I apply the changes back to viewmodel?" - not sure what's the source of the confusion, could you elaborate a bit more (and maybe edit the question itself, cause comments are transient)? There are different ways to do it, but, your call to the domain logic either returns something, or eventually produces an event or calls some callback, and you update the viewmodel based on that. – Filip Milovanović Aug 30 '23 at 20:34
  • 2
    @Liero: how do you bring the calculated values into the view model first place? Why not use the same mechanics to bring the recalculated values there again? – Doc Brown Aug 30 '23 at 21:10
  • 1
    This question is nigh unanswerable with the current information. The plain answer is "however you've designed your interaction with the domain". There are myriad architectures and approaches here and it's impossible to write an answer that is somehow specific to your case yet covers the entire range of possibilities. – Flater Aug 31 '23 at 00:57
  • @FilipMilovanović: "domain logic eventually produces an event or calls some callback" - seems doable, but isn't introducing `INotifyPropertyChanged` or similar callback for sake of updating UI violating the SoC? – Liero Aug 31 '23 at 06:26
  • @DocBrown: **"how do you bring the calculated values into the view model first place?"** I don't yet :) I would instantiate viewmodel and map domain object's properties to viewmodel. I could also just wrap the domain object's properties in the viewmodel's properties. **"Why not use the same mechanics to bring the recalculated values there again?"** Because the mapper maps everything and does not have information about what has been recalculated. – Liero Aug 31 '23 at 06:31
  • 1
    It depends on if you make the callback too UI-specific. If you have a structure that's like [presentation logic]-[application logic]-[domain logic], then such a callback would be required by a component in the application logic layer, but it would be passed in by something in the presentation layer. My answer [here](https://softwareengineering.stackexchange.com/a/420360/275536) tries to convey in a simplified way how this might work in Clean Architecture, maybe it'll be useful, at least on a conceptual level. – Filip Milovanović Aug 31 '23 at 06:43
  • @Flater: "There are myriad architectures and approaches here" The ideal answer would be some proposals then. I don't think there are too many suitable approaches here without significant drawbacks. – Liero Aug 31 '23 at 06:45
  • @Liero: *"Because the mapper maps everything and does not have information about what has been recalculated"* - and why is this an issue? Map all current values from the view to the domain object, just as if you would prepare it for saving, then recalculate and reload the data back. – Doc Brown Aug 31 '23 at 07:18
  • @Liero Listing questions are explicitly off topic here. – Flater Aug 31 '23 at 08:55
  • @Flater: I think the question is answerable, and it is not a "listing question". – Doc Brown Aug 31 '23 at 20:07

1 Answers1

1

Material, TimeToManufacture and Price should be just standard members of ProductOrder, and there should be a function ProductOrder.CalculateDerivedValues() which recalculates them based on other parameters within the object.

Step 1 looks like this (for the sake of simplicity synchronously, the real code can by asynchronous / event driven):

  productOrder= CreateProductOrderFromCatalog(...);
  productOrder.CalculateDerivedValues();
  mapper.MapToViewModel(productOrder, viewModel);
  PresentViewModel(viewModel);

Now the viewmodel is presented to the user, user changes some parameters and the UI initiates an event which cause to happen this as step 2:

  mapper.MapToDomainObjekt(viewModel, productOrder);
  productOrder.CalculateDerivedValues();
  mapper.MapToViewModel(productOrder, viewModel);
  PresentViewModel(viewModel);

It does not matter that the mapper does not know which attributes have been recalculated and which not. Just map the data completely in both directions. Changed values get an update with the new values, unchanged data will stay the same (they will be assigned just the previous values).

Of course, for the unlikely case you need some optimization which updates only the derived values, you may add a function UpdateOnlyDerivedValues(productOrder, viewModel) to the mapper and call that instead. But I would be extremely hesitant with that: though it does not duplicate the recalculation logic, it still duplicates the knowledge which values are derived, and which are not, which might become a source of errors.

Step 3 will then be the editing inside the view, and step 4 just

  mapper.MapToDomainObjekt(viewModel, domainObject);
  Persist(domainObject);
Doc Brown
  • 199,015
  • 33
  • 367
  • 565