6

To be able to scale I would like to use async programming. It works really well if I have to read something from db and push to frontend, however I do not know how to use it correctly in blobs of buniess logic that live their own lives. I have object, that has lots of properties that should be lazy loaded. For instance Order class that has Recipient, Payer, DeliveryAddress etc. It groups services that read something based on orderId.

In old times I would do: Payer payer = order.Payer; - it would check request cache and query base as needed. However now I have Payer payer = await order.Payer() and I hate it. It forces all my logic code to be asynchronous (because in accounting software Payer needs to be everywhere).

How to solve this problem?

4
  • To clarify it up a bit. I generally want it to be async, and lazy loaded. Just there is problem that usability suffers - no automapper, no linq, and code is not as clear as synchronous, because you have to await everything (await order.GetPayer()).DoSomething().
    – Shadow
    Commented Sep 16, 2016 at 12:07
  • Did you find a good solution for this?
    – tatigo
    Commented Nov 8, 2017 at 6:36
  • Nope :( You have to think if do you really need lazy loading per property. Usually I do something like await OrderFactory.Preload(ids, full), and then access them in sync manner.
    – Shadow
    Commented Nov 8, 2017 at 11:28
  • I'm using AsyncLazy<> . Need to have a cached value from async method, so basically would be using .Result to make the call synchronous , but wanted to avoid that
    – tatigo
    Commented Nov 8, 2017 at 20:53

4 Answers 4

7

Don't make things lazy that don't need to be. If you know that Payer will be needed the grand majority of the time then there is no value in making the loading of Payer be lazy. Instead make it eager on constructions of Order.

Lazyness is only of value for expensive things that may not happen or don't need to happen.


As a side note when doing a lazy load of one property it could be of value to use that load to also fetch the other properties. Each of lazy load will have significant overhead. So if you are doing a request for one property you may as well get them all.

1
  • In some subsystems (accounting) Payer will be loaded in majority of times, in others (sales analysis) it won't be required at all. Also I am doing some magic behind the scenes (for instance loading Payer or Recipient loads all entities related to order from Customer table etc.), so in majority of times it will just return Task.CompletedTask(payerCache);.
    – Shadow
    Commented Sep 16, 2016 at 11:56
2

Lazy loading need not be asynchronous. A synchronous implementation could look like

Payer _Payer;
public Payer Payer
{
    get
    {
        if (_Payer == null)
        {
            _Payer = LoadPayer();
        }
        return _Payer;
    }
}

It might include lock and another check if _Payer is still null after the lock was acquired.

On the other hand, the async idea is meant to spread thru all your code: go async all the way, not just with a few functions. If you don't like that idea, go with non-asynchronous lazy initialization!

10
  • 1
    It could do, if you don't care about thread safety and you're forced to use an old version of the framework. But if you have .Net 4.0 then Lazy<T> was added for a reason. Commented Sep 16, 2016 at 11:49
  • In my example method LoadPayer returns Task<Payer>, and it needs to be async (and method), as it hits database.
    – Shadow
    Commented Sep 16, 2016 at 11:53
  • @user2029276 - I have written many many applications where hitting the database does not mean the method that does it must be asynchronous. It is entirely possible (and in many cases advisable) to simply write a method that waits for the database call to finish before returning rather than returning a Lazy<> or similar stub. Indeed, I'd usually consider that default behaviour. The relevant question is, why does the client need to be able to asynchronously wait for the database call to return? Commented Sep 16, 2016 at 15:01
  • 1
    @Periata It needs to be async so webserver may use thread for other requests while waiting for database to return data.
    – Shadow
    Commented Sep 17, 2016 at 9:24
  • 1
    @PeriataBreatta I think that You do not really understand idea of async/await. Take a look there - msdn.microsoft.com/en-us/magazine/dn802603.aspx if your application is database bound it may scale much better without any (additional) concurrency.
    – Shadow
    Commented Sep 28, 2016 at 16:32
1

Just to add to Bernard's answer, the code for synchronous lazy loading can be made far simpler than what he shows:

private Payer _payer;
public Payer Payer => _payer ?? (_payer = LoadPayer());
1
  • Looks nice. Does that automagically include locking? Commented Sep 16, 2016 at 10:24
0

A better way is to seperate the concerns eg the Data fetch and the logic.

eg

var data = fetchData();
var logic = new Logic(data);
logic.DoLogic();
var changes = logic.GetChanges();
await persistChanges(changes).

This works better in DDD / CQRS and is one of the reasons for those designs but if you DO have separation of concerns than logic should be sync and IO async.

Its the intermingling of data and logic while convenient for small apps tend to lead to a mess in bigger ones. Separation of concerns here fixes the async logic issue not to mention better testing.

"In old times I would do: Payer payer = order.Payer;" This is a perfect example of mixed concerns as you mention it fetches ( eg lazy loads) the payer. Payer here is a PayerEntity.

2
  • 1
    Doesn't that lead to a situation where "fetchData" exactly needs to know beforehand which properties of the involved objects have to be loaded for DoLogic, and which are not required? The lazy loading strategy determines this almost automatically, without having that part of the business logic twice.
    – Doc Brown
    Commented Sep 1, 2019 at 7:59
  • You can have multiple fetches / run logic it does require smaller more clearly defined units of work so pick your poison. Also can use an aggregate which gets all info and cache it or it manages the fetchs and separates the logic . DDD is a pretty big topic to discuss here and it has its own . For me async / data access in the domain/logic is a red flag which leads to many issues. Commented Sep 3, 2019 at 6:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.