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?

Shadow
  • 363
  • 3
  • 10
  • 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 Sep 16 '16 at 12:07
  • Did you find a good solution for this? – tatigo Nov 08 '17 at 06: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 Nov 08 '17 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 Nov 08 '17 at 20:53

4 Answers4

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.

ratchet freak
  • 25,706
  • 2
  • 62
  • 97
  • 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 Sep 16 '16 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!

Bernhard Hiller
  • 1,953
  • 1
  • 12
  • 17
  • 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` was added for a reason. – Peter Taylor Sep 16 '16 at 11:49
  • In my example method `LoadPayer` returns `Task`, and it needs to be async (and method), as it hits database. – Shadow Sep 16 '16 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? – Periata Breatta Sep 16 '16 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 Sep 17 '16 at 09:24
  • @user2029276 - Why does the web server need to do that, though? I've been writing web applications that wait for db results rather than returning earlier since 1997 and have never once needed to do such a thing. Are you really working on a system that's so performance critical that *threads blocking for database access* aren't acceptable? – Periata Breatta Sep 19 '16 at 17:38
  • @PeriataBreatta Yes, of course. – Shadow Sep 22 '16 at 22:21
  • @user2029276 - I wouldn't say "of course". You're talking about an exceptionally high performance requirement. You're talking about performance requirements that aren't actually met by most of the most popular web sites on the planet. Realistically, what you're describing is only necessary if you're serving hundreds of requests *per second* and can't afford to scale horizontally to more servers. *Keeping the implementation simple* is usually much more important than the absolute performance of a single server. – Periata Breatta Sep 23 '16 at 04:57
  • @PeriataBreatta My webapp connects to many databases and 3rd party webservices. Also some of my clients run it on old hardware (even laptops). Some of these 3rd party webservices are poorly writen and take ages to respond, so I already need async code in my application. I would like to go along and make other not-yet-written code async, because why not? Implementation problems are highlighted here, and I am seeking solution for them. – Shadow Sep 26 '16 at 10:39
  • @user2029276 - hmm. when I've had that kind of problem I've taken the implementation out of the web server into a standalone service and then used a push notification system to let the client know when the request is finished, but I guess that C#'s async makes it easier to do this in the web server now, so maybe your way is better for this. Unfortunately, what you're looking it is as far as I can see necessary complexity for solving this problem, which is why I would have suggested simplifying by avoiding the concurrency if possible. It's unfortunate that you can't... :( – Periata Breatta Sep 27 '16 at 07:24
  • 1
    @PeriataBreatta I think that You do not really understand idea of async/await. Take a look there - https://msdn.microsoft.com/en-us/magazine/dn802603.aspx if your application is database bound it may scale much better without any (additional) concurrency. – Shadow Sep 28 '16 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());
David Arno
  • 38,972
  • 9
  • 88
  • 121
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.

user1496062
  • 111
  • 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 Sep 01 '19 at 07: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. – user1496062 Sep 03 '19 at 06:14