skip to content
Relatively General .NET

Modeling social network connection with RavenDB

by Oren Eini

posted on: August 02, 2021

I got an interesting scenario that I thought would make for a good blog post. Given a social network similar to Twitter, how would you deal with the social aspect of it? In other words, how would you model the following relationship between users.In pretty much all social media platforms, the following relationship is unidirectional. I’m following The Wheel Of Time, but it isn’t following me back. That is a very important aspect for modeling as well as the maximum number of accounts that you can follow. Both Facebook and Twitter will limit the number of accounts that you can follow to 5,000. All of those gives us a pretty good operating environment. To model the following relationship in a social network I’ll use the following option: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public record class FollowingRelationship (string UserId, DateTime StartedFollowingAt); public record class Fellowship (string UserId, FollowingRelationship[] Following); /* // As JSON "UserId": "ayende", "Following": [ { "UserId": "ravendb", "StartedFollowingAt": "2009-01-01" }, { "UserId": "TheWheelOfTime", "StartedFollowingAt": "2021-08-01" } ] */ view raw social.cs hosted with ❤ by GitHub In other words, the recommended approach is to store all the accounts that a user follows in a single document. The fact that an account is limited to following a maximum of 5,000 accounts gives us a hard limit on the size of that document (and most accounts will follow far less).Note that the code above is also pretty much the simplest possible option for how to record the following relationship. In this case, the simplest and best option are one and the same. Let’s consider what operations we need to apply on the following relationship:Find all the followers of a particular account. from Fellowships where Following[].UserId = $userIdFind the number of followers per account: from Fellowships group by Following[].UserId  where Following[].UserId = $userId select count(), Following[].UserId Finding all the users that a particular user is following is simply loading the document and looking at the Following property, so I’m skipping that. The key here is that this is going to be simple to build and use, scalable because different users can start following accounts independently from one another and doesn’t require to do anything fancy.

Upload files with drag & drop or paste from clipboard in Blazor

by Gérald Barré

posted on: August 02, 2021

To upload a file you can use the FileUpload control. This control generates a <input type="file"> element and allows you to upload a file. To get a good user experience, you also need to support dropping files from the explorer or pasting images from the clipboard. The final result should loo

Open for extension, closed for modification as an architectural pattern

by Oren Eini

posted on: July 30, 2021

The Open Closed Principle is part of the SOLID principles. It isn’t new or anything exciting, but I wanted to discuss this today in the context of using that not as a code artifact but as part of your overall architecture.The Open Closed Principle states that the code should be opened for extension, but closed for modification. That is a fancy way to say that you should spend most of your time writing new code, not modifying old code. Old code is something that is known to be working, it is stable (hopefully), but messing around with old code can break that. Adding new code, on the other hand, carry far less risk. You may break the new things, but the old stuff will continue to work. There is also another aspect to this, to successfully add new code to a project, you should have a structure that support that. In other words, you typically have very small core of functionality and then the entire system is built on top of this.  Probably the best example of systems that follow the Open Closed Principle is the vast majority of PHP applications. Hold up,I can hear you say. Did you just called out PHP as an architectural best practice? Indeed I did, and more than that, the more basic the PHP application in question, the closer it is to the ideal of Open Closed Principle.Consider how you’ll typically add a feature to a PHP application. You’ll create a new script file and write the functionality there. You might need to add links to that (or you already have this happen automatically), but that is about it. You aren’t modifying existing code, you are adding new one. The rest of the system just know how to respond to that and handle that appropriately. Your shared component might be the site’s menu, a site map and the like. Adding a new functionality may occasionally involve adding a link to a new page, but for the most parts, all of those operations are safe, they are isolated and independent from one another.In C#, on the other hand, you can do the same by adding a new class to a project. It isn’t at the same level of not even touching anything else, since it all compiles to a single binary, but the situation is roughly the same. That is the Open Closed Principle when it applies to the code inside your application. What happens when you try to apply the same principle to your overall architecture?I think that Terraform is a great example of doing just that. They have a plugin system that they built, which spawns a new process (so completely independent) and then connect to it via gRPC. Adding a new plugin to Terraform doesn’t involve modifying any code (you do have to update some configuration, but even that can be automated away). You can write everything using separate systems, runtime and versions quite easily.If we push the idea a bit further, we’ll discover that Open Closed Principle at the architecture level is the Service Oriented Architecture.  Note that I explicitly don’t count Microservices in this role, because they are usually intermixed (yes, I know they aren’t supposed to, I’m talking about what is). In those situations, adding a new feature to the system would involve adding a new service. For example, in a banking system, if you want to add a new feature to classify fraudulent transactions, how would you do it?One way is to go to the transaction processing code and write something like: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public void processTransactions(HttpServletRequest request, HttpServletResponse response) { String src = request.getParameter("from"); String dst = request.getParameter("to"); int amountInCents = Integer.parseInt(request.getParameter("amount")); /// new code here TransactionProcessingFactory fact = new TransactionProcessingFactory(); // will throw if fraud fact.createProcessor().process(new Transaction(src, dst, amountInCents/1.0)); // new code here /* ** Rest of the actual functionality **/ } view raw txn_proc.java hosted with ❤ by GitHub That, of course, would mean that you are going to have to modify existing code, that is not a good idea. Welcome to six months of meeting about when you can deploy your changes to the code. On the other hand, applying the Open Closed Principle to the architecture, we won’t ever touch the actual system that process transactions. Instead, we’ll use a side channel. Transactions will be written to a queue and we’ll be able to add listeners to the queue. In such a way, we’ll have the ability to add additional processing seamlessly. Another fraud system will just have to listen to the stream of messages and react accordingly.Note that there is a big difference here, however, unlike with modifying the code directly, we can no longer just throw an exception to stop the process. By the time that we process the message, the transaction has already been applied. That requires that we’ll build the system in such a way that there are ways to stop transactions after the fact (maybe by actually submitting them to the central bank after a certain amount of time, or releasing them to the system only after all the configured endpoints authorized it).At the architecture level, we are intentionally building something that is initially more complex, because we have to take into account asynchronous operations and work that happens out of band, including work that we couldn’t expect. In the context of a bank, that means that we need to provide the mechanisms for future code to intervene. For example, we may not know what we’ll want the additional code to do, but we’ll have a way to do things like pause a transaction for manual review, add additional fees, raise alerts, etc.  Those are the capabilities of the system, and the additional behavior would be policy around building that. There are other things that make this very attractive, you don’t have to run everything at the same time, you can independently upgrade different pieces and you have clear lines of demarcation between the different pieces of your system.

System.Threading.Tasks.Task isn’t an execution unit

by Oren Eini

posted on: July 29, 2021

From a conceptual model, a thread and a task are very similar. That is very much by design, since the Task is meant to allow you to work with asynchronous code while maintaining the illusion that you are running in a sequential manner. It is tempting to think about this in terms of the Task actually executing the work, but that isn’t actually the case.The Task doesn’t represent the execution of whatever asynchronous process is running, the Task represent a ticket that will be punched when the asynchronous process is done. Consider the case of going to a restaurant and asking for a table, if there isn’t an available table, you cannot be seated. What the restaurant will do is hand you a pager that will buzz when the table is ready. In the same sense, a Task is just such a token. The restaurant pager doesn’t means that someone is actively clearing a table for you. It is just something that will buzz when a table is ready.A code sample may make things clearer: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters public class Restaurant { private int _occupancy; private List<(TaskCompletionSource<object>, int)> _waiters = new List<(TaskCompletionSource<object>, int)>(); public Task GetTable(int numberOfDiners) { lock (this) { if(_occupancy+ numberOfDiners < 60) { _occupancy += numberOfDiners; return Task.CompletedTask; } var tcs = new TaskCompletionSource<object>(); _waiters.Add((tcs, numberOfDiners)); _waiters.Sort((x, y) => x.Item2.CompareTo(y.Item2)); return tcs.Task; } } ​ public void ReleaseTable(int numberOfDiners) { lock (this) { _occupancy -= numberOfDiners; while (_waiters.Count > 0) { if(_waiters[0].Item2 + _occupancy > 60) break; var tcs = _waiters[0].Item1; _occupancy += _waiters[0].Item2; _waiters.RemoveAt(0); tcs.SetResult(null); } } } } ​ view raw Restaurant.cs hosted with ❤ by GitHub In this case, we are manually coordinating the Task using its completion source and you can see that the Task instance that was handed when trying to get a table doesn’t actually start anything. It is simply waiting to be raised when called.That is an important aspect of how System.Threading.Tasks.Task works, because it is usually in contrast to the mental model in our head.

How Does the StringBuilder Work in .NET? (Part 3)

by Steve Gordon

posted on: July 28, 2021

Part Three:  How Appending Works and the StringBuilder Expands So far in this series, we’ve learned when we should consider using StringBuilder in our code and learned about the memory overhead of using a StringBuilder. It’s now time to learn how the StringBuilder can “expand” its capacity and support appending string data efficiently. As with […]

Architecture foresight: Put a queue on that

by Oren Eini

posted on: July 28, 2021

If you build any kind of non trivial system, one of the absolutely best things that you can do for the long term health of your system is to move all significant processing to sit behind a queue.  That is one of those things that is going to pay massive dividends down the line as your system grows. Basically, any time that you want to do something that isn’t “pull data and show it to the user” or “store the data immediately”, throwing a queue at the problem is going to make things easier in the long run.Yes, this is a bold statement, and I’m sure that you can see that this may be abused. Nevertheless, if you’ll follow this one rule, even if you misuse it, you are likely going to be better off than if you don’t. I should probably explain.When I’m talking about using a queue, I’m talking about moving actual processing of an operation from the request handler (controller / action / web sever ) to popping a message from a queue and processing that. The actual queue implementation (SQS, Kafka, MSMQ, ring buffer) doesn’t actually matter. It also doesn’t matter if you are writing to the queue in the same process and machine or a distributed system. What matter is that you can created a break in the system between three very important aspects of command processing:Accepting a request.Processing the request.Sending the result of the request back.A system without a queue will do all of that inline, in this manner: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters @app.route('/purchase-policy') def purchase_policy(): user_id = request.args.get("user_id") # load the user's details # process the policy, evaluate terms return "Thank you for your purchase", 201 view raw inline.py hosted with ❤ by GitHub What is the problem here? If the processing of the request is complex or takes some time, you have an inherent clock here. At some point, the client is going to timeout on the request, which lead to things like this:On the other hand, if you put a queue in the middle, this looks like this: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters @app.route('/purchase-policy') def purchase_policy(): req_id = uuid.uuidv4() user_id = request.args.get("user_id") other_details = ... # get them somehow put_on_queue(req_id, user_id, other_details); return req_id, 202 # accepted, not processed @app.route('/purchase-policy-status') def purchase_policy_status(): req_id = request.args.get("req_id") if load_req(req_id) is None: return "Please wait, processing", 203 return "Thank you for your purchase", 201 # created # called somewhere else, not part of the request processing def queue_callback(msg): # load the user's details # process the policy, evaluate terms save_data(msg.req_id) view raw queued.py hosted with ❤ by GitHub Note that there is a separation in the processing of the request and sending the accepted answer to the customer.What is the impact of this change? Well, it is a bit more complex to manage in the user interface. Instead of getting the response for the request immediately, we have to fetch it in a separate operation. I’m typically really strict on policing the number of remote calls, why am I advocating for an architectural pattern that requires more remote calls?The answer is that we build, from the first step, the ability of the system to delay processing. The user interface no longer attempts to pretend that the system reacts instantly, and have far more freedom to change what we do behind the scenes.Just putting the operation on a queue gives us the ability to shift the processing, which means that we can:Maintain speedy responses and responsive system to the users.Can easily bridge spikes in the system by having the queue flatten them.Scale up the processing of the operations without needing to do anything in the front end.Go from a local to a distribute mechanism without changing the overall architecture (that holds even if you previously held the queue in memory and processed that with a separate thread). Monitor the size of the queue to get a really good indication about where we are at in terms of load.Gain the ability to push updates to the backend seamlessly.At more advanced levels:Can copy message to an audit log, which gives great debugging abilities.Can retry messages that failed.There are whole patterns of message based operations that are available to you, but the things that I list above are what you get almost for free. The reason that I think you should do that upfront is that your entire system would already be used to that. Your UI (and users’ expectations) would already be set to handle potential delays. That gives you a far better system down the line. And you can play games on the front end to present the illusion that operations are accepted on the server (in pending status) without compromising the health of the system.In short, for the health of your system, put a queue on that, your future self will thank you later.One final word of warning, this apply to operations, not queries. Don’t bother putting queries through the queue unless they are intended to be very long lived / complex ones.

Responsibility Abdication: The path to create flexible business system

by Oren Eini

posted on: July 27, 2021

I had a long conversation with a dev team that are building a non trivial business system. One of the chief problems that they have to deal with is that the “business logic” that they are asked to work with is extremely mutable, situation dependent and changes frequently. That isn’t a new compliant, of course, but given that I have run into this in the past, I can deeply emphasize. The key issue is that the business rules (I refuse to call it logic) are in a constant state of flux. Trying to encode them into the software itself leads to a great deal of mess in both the code and architecture.For example, consider the field of insurance. There are certain aspects of the insurance field that are pretty much fixed in stone (and codified into law). But there are (many) others that are much more flexible, because they relate to the business of selling insurance rather than the actual insurance itself. Because certain parts of the system cannot change (by law), all the modifications happen in other places, and those places see a lot of churn. A marketing executive came up with a brilliant idea, let’s market a health insurance policy for young athletic people. This is the same as the usual young policy, but you get a 5% discount on the monthly premium if you have over 20 days in the month with over 10,000 steps recorded. Conversely, you get penalized with 5% surcharge if you don’t have at least 15 days of over 10,000 steps recorded. Please note that this is a real service and not something that I just made up. Consider what such a feature means? We have to build the integration with FitBit, the ability to pull the data in, etc. But what happens next? You can be sure that there are going to be a lot more plans and offers that will use those options. You can envision another offer for a policy that gives discounts for 40+ who jogs regularly, etc. What does this kind of thing looks like in your code? The typical answer is that this can be one of a few options:Just Say No – in some IT organizations, this is just rejected. They don’t have the capacity or ability to implement such options, therefor the business won’t be able to implement it.Yes man – whatever the business wants, the business gets. And if the code gets a little ugly, well, that is life, isn’t it? Structured – those organizations were able to figure out how to divide the static pieces and the frequently changing parts in such a way that they can ensure long term maintainability of the system.In many cases, organizations start as the 2nd option and turn into the 1st. In the early 2000, cellular phones plans in Israel were expensive. A family plan could cost as much as a mortgage payment. I’m not kidding, it was really that bad. One of the cellular companies had an inherent advantage, however. They were able to make new offers and plans so much faster than the companies. Summer Vacation plan for your teenagers – speak free after midnight with 50 texts a week.Off hours dedicated phones discounts – you can talk to 5 phone numbers between 20:00 – 08:00 and on weekends for fixed price.All sort of stuff like that, and that worked. Some people would switch plans on a regular basis, trying to find the optimal option. The reason that this company was able to do that had to do with the manner in which they did billing.What they did was quite amazing, even decades later. Their billing systems aggregated all the usage of a particular plan based and pushed that into a report. Then there was a directory filled with VBScript files that they would run over the report. The VBScripts were responsible for apply the logics for the plans. The fact that they wrote them in VBScript meant that they had a very well defined structure. There was all the backend work that gathered the data, then they applied the policies in the scripts. Making those kind of changes and introducing new policies was easy.If the technique is familiar to you, that is because I talked about this in the past. In fact, I wrote a book about it. But this isn’t the time to talk about a book a dozen years old or a story from twenty years ago. Let’s talk about how we can apply this today, shall we?For scripting, I’m going to use MoonSharp, which is a managed Lua implementation. Lua is a great scripting language, it is quite capable and at the same time usable for people without too much technical knowledge. Among other things, it also offers builtin debugging support, which can be a crucial feature for large scale systems.At any rate, let’s consider the following logic: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters if policy.Type == PolicyType.House and policy.address.country == 'Germany' and policy.address.zip == '50374' then policy.adjust_policy('flood risk', 50) end view raw flood_prone_places.lua hosted with ❤ by GitHub As you can see, this script raise the monthly rate for a house insurance policy in a particular location. To execute this code, you’ll need something like: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters UserData.RegisterType<Policy>(); UserData.RegisterType<Address>(); UserData.RegisterType<PolicyType>(); Script script = new Script(); script.Globals["PolicyType"] = UserData.CreateStatic<PolicyType>(); script.Globals["policy"] = policy; script.DoString(scriptCode); view raw run_script.cs hosted with ❤ by GitHub Let’s look at a slightly more complex example, implementing the FitBit discount: This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters days_with_10K_plus_steps = 0 for i, day in ipairs(fitbit.period) do if day.number_of_steps > (10 * 1000) then days_with_10K_plus_steps = days_with_10K_plus_steps + 1 end end if days_with_10K_plus_steps >= 20 then policy.apply_discount('Walked 10K or more for 20+ days this month", 0.05) # 5% discount, yeah elseif days_with_10K_plus_steps < 15 then policy.apply_discount('Walked 10K or more for less than 15 days this month", -0.05) # 5% penalty, boo! end view raw fitbit_discount.lua hosted with ❤ by GitHub Those are the mechanics of how this works. How you can use MoonSharp to apply arbitrary logic to a policy. As I mentioned, I literally wrote a book about this, discussing many of the details you need to create such a system. Right now, I want to focus on the architecture impact.The kind of code we’ll write in those scripts is usually pretty boring. Those are business rules in all their glory, quite specific and narrow, carefully applied. They are simple to understand in isolation, and as long as we keep them this way, we can reduce the overall complexity on the system.Let’s consider how we’ll actually use them, shall we? Here is what the user will work with to customize the system. I’m saying user, but this is likely going to be used by integrators, rather than the end user. That data is then going to be stored directly in our Policy object, and we can apply it as needed. A more complex solution may have the ability to attach multiple scripts to various events in the system. This change the entire manner of your system, because you are focused on enabling the behavior of the system, rather than having to figure out how to apply the rules. The end result is that there is a very well defined structure (there has to be, for things to work) and in general an overall more maintainable system.