Single File Process with ASP.NET Web APIs and MediatR

Introduction

After working for lots of companies, I keep witnessing the same development problems again and again. After tons of videos and readings on CQRS, the mediator pattern, specifically the amazing MediatR package, and with thousands of working experience, I came up with a nice solution.

Over the past 3 years or so, I developed the “Single File Process” design pattern, to help me and my teams getting faster in developing new projects and features, yet keeping the code clean, easy to understand and maintainable.

I tested this design in several projects in different sizes, with developers from several backgrounds and experties, and it was a success. A junior developer once told me that the “Single File Process” helped him a lot getting up to speed with his team and it was quite easy to be productive and still producing clean code in a quite complex system.

This article is my attempt to explain what “Single File Process” is, what it tries to solve, how it puts structure in your code, and how things could get much easier and smoother.

Problems

Let’s start with a small list of problems I faced over and over again, which were my motivation to find a solution:

  • Magic: Using some packages that inject themselves in the request/response pipeline could make you more productive, yet they are very hard to debug, not easy to understand, and have lots of side-effects.
    Usually as a developer, I want to debug my code sequentially and see everything going on, starting from the request and ending with the very last response, but with things like “AutoMapper” and “FluentValidation” you would see things “magically” happen, and finding the code responsible for such is quite a challenge in big projects where lots of mappers and validators are usually put together.
  • no-KISS: Our code is our poem, we are more than developers, we are artists. That could be true to an extend, but basically we are hired to deliver value, to build things in a justified way in time.
    Sometimes we lose this campus and go wild, sometimes too academic or by the book, which could be a good thing, but could also mean bad maintainability due to complexity, and that’s more damaging than writing “beautiful” code.
    I worked in a company that took me, and that was the average there, almost 2 months to be fully onboarded. Besides setting up my development environment, getting access to company’s stuff, etc, understanding the code was very challenging, and I’m a senior developer. Make the math and imagine a new junior joiner, multiply the numbers at best without even counting the frustration and pressure.
    The code was too academic, too beautiful, for a very simple application.
  • Pollution: With the MVC pattern everywhere, we tend to put Model classes in the ASP.NET project “Models” root folder, and every time we need a small change in one or introducing a new endpoint, we find tons of other models that look alike.
    It’s common to see models like UserDto, UserModel, User, CreateUser, UpdateUser, UserDetail, UserDetails, etc, And all hell breaks loose when we try changing one of them, let’s address this in the next bullet.
  • Reusability -f: I don’t know why we tend to -f (force) reusability, we think it’s a good achievement and it saves time hence costs. But in reality, any reusable block of code tends with time to favor one use over another, and we pay even more tech-dept costs to break this reusability in the long run.
    Besides, I might accept the fact that we write “some” logic to be reused, but I don’t accept request Models to be reusable, every endpoint/API should have its own Request types. If we have endpoints that receive the same exact request, then we have a bad business design.
  • Believes: Systems are becoming more and more complex, and users are expecting more and more from us. To face this challenge with limited time and labor, developers need to be more “mechanical”, give more focus to the business and the value we deliver to our clients, more thinking about the features users need, and how to coin this into code.
    But, there are others who have different believes, they have their own technical preferences of how we should organize and write our code. Those developers tend to lose the vision pretty fast, and their value drops quite dramatically, their code turns obsolete in no time yet it looks pretty. Tunnel vision is an example of this case where developers focus on the wrong thing.

Those are some of the problems I’m trying to solve, and you can still derive many more out of them. At the end, they all end up creeping in your code, adding more dept that costs you more, and hence cutting from the profitability of your app.

Single File Process

Enough said, lets drill into the details of the “Single File Process”. Basically, it’s a single class that represents a single business process. Making an order is a process, resetting your password is a process, fetching page #3 of the products is a process, you get the idea.

Every process class contains 5 nested public classes, in this order:

  1. Request class (required): this class represents the coming request, with data coming from the body, the URL, headers, etc. passed in.
    It inherits from IRequest<Response> (from MediatR)
  2. Response class (optional): this one represents the output of the process, for example a list of products, or just a boolean that indicates the success of the operation
  3. Handler class (required): this is the engine that runs the process, it converts the inputs (Request) into outputs (Response) by doing the necessary work.
    This is the one that occupies most of the space of the parent process class, which gives clear indication on how complex your operation is.
    It inherits from IRequestHandler<Request, Response> (from MediatR)
  4. Validator class (optional): if you want to validate anything regarding the coming Request, this is the place for it. You can of course do this in the Handler, but I usually use FluentValidation for such, it’s still magic but at least it’s organized and I can see it in the same process scope.
  5. Mapper class (optional): if you want to transform Request to Response, or converting others in the Handler, this is the place to do it. You can also do this in the Handler, but I use AutoMapper for such, and at least I have the related mapping code in the same process scope.

With these 5 classes in place and by only looking at the process class, you can easily understand everything related to the process in a single file, you don’t need to look elsewhere. Everything related to this business process is encapsulated in this singe file.

Hello world

To make it clear, let’s work with an example. Reset password could be a nice one here. Typically, user resets his password by passing in his email address as an input and expects an email being sent to his email inbox with a link for assigning a new password.

In this example, the Single File Process for this case will be similar to the following:

using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using MediatR;

namespace SingleFileProcessDemo.Processes.Account
{
    public class ResetPasswordProcess
    {
        public class Request : IRequest<Response>
        {
            public string Email { get; set; }
        }

        public class Response
        {
            public bool Success { get; set; }
            public bool EmailSent { get; set; }
        }

        public class Handler : IRequestHandler<Request, Response>
        {
            public Task<Response> Handle(Request request, CancellationToken cancellationToken)
            {
                // do some authentication ...
                // retrieve from db ...
                // generate reset link ...
                // send email to user ...

                return Task.FromResult(new Response
                {
                    Success = true,
                    EmailSent = true,
                });
            }
        }

        public class Validator : AbstractValidator<Request>
        {
            public Validator()
            {
                RuleFor(x => x.Email).NotEmpty();
            }
        }
    }
}

Controller

In case of handling a user request, you will typically trigger the process from the corresponding controller. For our example of resetting password, it would be like this:

using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using SingleFileProcessDemo.Processes.Account;

namespace SingleFileProcessDemo.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class AccountController : ControllerBase
    {
        private readonly IMediator _mediator;

        public AccountController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpPost("reset-password")]
        public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordProcess.Request request, CancellationToken cancellationToken)
        {
            var response = await _mediator.Send(request, cancellationToken);
            return Ok(response);
        }
    }
}

Notice here that ResetPasswordProcess.Request won’t be used anywhere else, it’s quite bound to this, and only this, endpoint. This helps a lot in debugging as you can clearly see who is responsible for what.

You don’t need anything extra injected in your controller, except the IMediator. This makes your controller very thin.

This controller example is basically what you would do in all cases. Typically, a Request being added as an action parameter and flagged as FromBody, FromRoute, etc, and then pass such request to the IMediator.Send method, and handle the results.

IHostedService

Same like Controller, if you are handling a background job, you would do exactly the same thing, except of course that you need to initialize your own Request, send it via IMediator.Send, and finally handle the results. Basically the same, but in stead of having the Controller to be the process host/trigger, it would be your IHostedService.

Processes overview

I always group my process classes in a root folder called “Processes”, which inside I include one level of feature-based folder, something like “Processes/Account” or “Processes/Products”.

By implementing the Single File Process pattern and organizing your code like this, you can easily at glance see what kind of business workflows your app handles. for example:

This will help you also finding the responsible process easily and understanding the code quickly, yet better “onboarding”.

Final notes

  • I like adding the suffix “Process” to each process class, as I don’t like ending up with a verb-based class like “GetProfile“. Actually, I like adding suffixes to every class/type I have according to its purpose, that way it’s much easier for me to know exactly what it does. Things like: AccountController, UserEntity, PageModel, AuthConstants, CategoryService, etc. And I like grouping them in their own folders, say “Controllers”, “Services”, “Entities”, “Constants”, “Models”, etc, besides of course “Processes”.
    This helps me a lot being productive, fast in finding the file I’m looking for, and easy to understand for everyone.
  • By checking the size of the Handler class in every Single File Process, I know immediately if my process is in the right manageable code size, or I might need to rethink and redesign.
    Sometimes I break the code in the Handler into multiple inner private methods, sometimes I break a huge process into multiple, it all depends on the business, but the thing that I try my best, is avoiding overfitting.
  • Unit testing becomes much easier and direct. By focusing on testing the Handle method of the Handler class, you will cover most of your code and almost all of your business.
  • Single File Process is not a silver bullet, yet I didn’t face a case that it cannot manage, but if that happens to you, feel free to skip. I can think of things like Saga’s and Workflows where huge lines of code and big status check trees need to be written together. Those cases will be little though, and I still think they could be transformed to a better sized processes. The Single File Process gives you great structure and important indicators about your business and process sizing
  • I used the Process keyword here as it’s not common in the web development world, besides, every Single File Process handles a single feature unit in your app, that’s why I found Process a nice term.
  • Besides ASP.NET Core and MediatR, in totally different technologies or languages, I still think you can benefit from the Single File Process pattern and it could be implemented easily. At the end, it’s a CQRS implementation with Mediator pattern in a single file represents a small business unit.
  • Single File Process is a dramatic change in our way of coding. Give it sometime and take it easy, try it out in a small project and see how it goes. Changes could be scary, and I heard lots of rejections the first time I introduced this to colleagues, but later they saw the benefits. Give it time!

Conclusion

Single File Process pattern is a way to encapsulate business feature units into corresponding process files, making it easy for everyone to understand and be productive. It puts things in order, forces a structure, makes your code clear, easy to understand, easy to maintain and easy to grow fast.

I got inspired a lot by this talk of Jimmy Bogard (MediatR author) on Vertical Slice Architecture, I highly recommend watching it:


You can find an example code in this repo, I included more complex cases:
https://github.com/mkinawy/single-file-process-demo

I hope you enjoyed reading this, feel free to give your feedback, I would love hearing your thoughts on this.

طار مع السحاب

طارت الوظيفة

لم يكن هذا الصباح مثل غيره ، بعد أن تلقيت رسالة من زميل قديم يسألني كيف يمكنه أن يصبح مبرمجاً مثلي وبالتحديد في نفس تخصصي في ASP.NET … لم أستغرب الطلب إذ أتلقى مثل هذه الأسئلة من آخرين من آن إلى آخر ، لكن وجه استغرابي هو أن تأتيني من هذا الزميل ذو ال٤٥ عاماً والذي يعمل في مجال IT منذ ٣٠ عاماً ، ما بين إدارة الشبكات وإعداد الخوادم servers وضبط الـ data centers وتنصيب قواعد البيانات المركزية وغيرها من الأمور الفنية الدقيقة التي تحتاج خبرة وعلم كبيرين.

كنت أعلم جيداً ما لزميلي هذا من معرفة وخبرة بمجاله وعمله في شركات كبرى في عدة مدن مهمة آخرهم لندن ثم أمستردام ، ولكن لم أكن أعلم ما يعانيه زميلي هذا بسبب “السحاب”.

حتى أتمكن من إعطائه أفضل نصيحة ، أردت أن أعرف أكثر عن خبرته ومهاراته وكذا عن دوافعه ومحفزاته ، وبعد أن إلتقينا في موعد ، علمت أن هناك احتمال كبير أن تقوم شركته بالاستغناء عنه أو على الأقل نقله إلى قسم آخر لا يمت لخبرته بشيء وذلك حال لو لم يتمكنوا من التخلص منه قانونياً أو لو لم يصلوا إلى اتفاق. وعلمت أيضاً أن هذا تم بالفعل مع عدد من زملائه في ذات المكان وفي الأماكن السابقة ، وهو ما صار يكد عليه نومه ويبقيه مستيقظاً مهموماً ليال طوال.

الشركات صارت تتجه بخطى متسارعة ثابتة نحو الحوسبة السحابية cloud computing عبر نقل كل البنى التحتية infrastructure إلى السحاب cloud ، وما يستتبع ذلك من قتل مستمر لوظائف الـ IT وتحويل كثير من مسؤولياتهم تدريجياً لفرق البرمجة ذاتها أو ما يعرف بـ DevOps … ليتبق في النهاية “صناع الطرابيش” اللذين كانوا يمتلكون صناعة مزدهرة ، ولكن تتغير الأحوال والأذواق والقوانين ، فتصبح صناعة عفا عليها الزمن obsolete.

موفري الخدمة من شركات الحوسبة الكبرى يسوقون بشدة للسحاب ولمفهوم الـ DevOps ويصورون الشركات الغير سحابية كأصحاب الكهف ، ويدعونهم باستماتة للتخلص من بنيتهم التحتية ونقل كل أعمالهم وبياناتهم إلى السحاب ، حتى يحدث ما أسميه “زواج المسيار” vendor marriage أو الزواج من موفر الخدمة ، فلا يقدر هؤلاء الزبائن أبداً من الإفلات من الشباك ويبقون رهينة للسحاب إلى الأبد ، ويبقى تدفق النقود مستمراً.

يريد زميلي أن يتطور محاولاً بث الروح من جديد في عمله ، ولكن هل يقدر وقد امتلأ عقله بالفعل بسنوات طوال من العلم والعمل والخبرات المتسعة في مجال صعب معقد … إنها لعنة التخصص وتقسيم العمل. كلي أمل أن يقدر وسأساعد ما استطعت ، ولكن النوايا لا تكفي والبوادر لا تطمئن.

طارت النقود

منذ أشهر قليلة ، تحديداً فبراير ٢٠١٩ ، أعلنت Microsoft Azure عن توفير خدمة Azure Cost Management لعملائها في أوروبا ، وهي خدمة تمكن العملاء من متابعة دقيقة لتكاليف الخدمات المستخدمة من الخدمات السحابية Azure … في السابق كان هناك خواص مماثلة لمراقبة النفقات لكنها لم تكن بذات العمق والتخصص.

تلك الخدمة الجديدة مكنت أحد بنوك هولندا من تتبع بعض التكاليف ومن ثم تقليصها بنسبة ٣٠٪ ، من ٥٠ ألف يورو مدفوعات شهرية إلى ٣٥ ألف يورو ، فقط عبرإيقاف خاصية واحدة هي الـ Replication في خدمة قواعد البيانات Azure Cosmos DB.

الطريف أن هذه الخدمة من قواعد البيانات Azure Cosmos DB كان يتم تسويقها بأنها مجانية تماماً طالما لم يتخط المستخدم حداً معيناً من الاستهلاك والمصادر ، وذلك نكاية  في الغريم التقليدي لخدمة قواعد البيانات من أمازون Amazon AWS Document DB والذي يوفر خواص ومزايا شبيهة … كانت ميزة “المجانية في البداية” ما دفع هذا البنك إلى تبني هذه الخدمة ، لكنه لم يكن يعلم أن الضبط الافتراضي لهذه الخدمة سيكون شاملاً لخصائص داخلية مدفوعة وأيضاً مكلفة جداً مثل الـ Replication ، ولا أعتقد أبداً أن في الأمر حسن نية من Microsoft.

خدمة Azure Cost Management التي مكنت هذا البنك من توفير أموال طائلة ، ربما أيضاً مكنت أخرون من فعل المثل ، هو الأمر الذي انعكس جلياً على تناقص إيرادات وتباطؤ في نمو الخدمات السحابية Azure طوال هذا العام … ولكن ماذا عن الأعوام السابقة؟ هذا البنك مثلاً يدفع مقابل هذه الخدمات منذ ٣ سنوات … وأيضاً ماذا عن الخدمات الأخرى التي تبدوا “مجانية في البداية” مثل Azure Functions و AWS Lambda؟ أظن أن الكثيرين في انتظارهم “خازوق”.

من ناحية أخرى ، ما الذي دفع هذا البنك إلى تبني السحاب؟ علماً بأن ٩٩٪ من عملاء البنك هولنديين مقيمين بهولندا وعددهم في حدود ٥٠٠٠ عميل فقط ، وأن البنك لديه data centers محلية ضخمة ومهيئة لكافة الأغراض والتي بالفعل يعمل منها عدد من تطبيقات البنك المهمة ، ولدى البنك بالطبع فريقاً متخصصاً على أعلى مستوى في إدارة وضبط وتأمين هذه الـ data centers. البنك لا يحتاج scalability فعدد عملائه ثابت ومحدود ، ولا يحتاج geo-availability مطلقاً فكل العملاء تقريباً في ذات الدولة ، ولكنه قرر في النهاية السير في هذا الطريق ، ومن ثم دفع أجور باهظة لمهندسين ومصممين متخصصين في السحاب cloud architect ، وأجور فريق متخصص للدعم platform team ، بالطبع غير أجور التدريب وتكاليف استهلاك الخدمة ذاتها.

شركة كبرى مثل Indeed والتي تعتبر محرك البحث الأهم للوظائف والتي تعمل في أكثر من ٦٠ دولة بـ ٢٨ لغة ،  قررت الابتعاد مطلقاً عن السحاب والتركيز على بناء بنيتها التحتية الخاصة حول العالم ، والسبب والدافع الأهم ببساطة هو توفير النفقات.

صديق لي ، لديه موقع إليكتروني كان ينوي استضافته على أحد موفري خدمات السحاب ، طلب مني رأيي في هذا … فقمنا سوياً بالحسابات ووجدنا أن موقعه سيكلفه شهرياً قرابة ١٠٠ يورو عند استضافته على السحاب ، بينما استضافة تقليدية ستكلفه ١٠ يورو في الشهر … بالطبع القرار صار جلياً.

طار الأمان

في حلقة أمازون من برنامج دليل الوطنية Patriot Act لحسن منهاج على نتفلكس Netflex – الحلقة يمكن مشاهدتها هنا على اليوتيوب – بجانب حديثه عن عملاق التسوق أمازون Amazon وممارساته الاحتكارية وظروف العمل السيئة به وتفحله كشركة فوق القانون والدول ، تطرق منهاج أيضاً إلى الخدمات السحابية المقدمة من أمازون Amazon Web Services – AWS.

على عكس فيسبوك وجوجل ، أمازون تراقب المستخدمين من أجل منفعتها الخاصة … عبر تتبع سلوكياتهم وتحليل أنماط استهلاكهم ، يمكن لأمازون عمل استهداف للأفراد واحتكار سلع وأسواق كاملة … أمازون تمتلك أكثر من ٨٠٪ من الحصة السوقية للحوسبة السحابية ، ملايين من المواقع والملفات وقواعد البيانات يتم استضافتها على خوادم أمازون ، كمية مهولة من المعلومات عن الأفراد والشركات تمر عبر شبكات أمازون.

لقد أصبحنا في زمن المعلومات فيه هي السلاح الأقوى ، ومن يمتلك هذه المعرفة يمتلك القوة … ومن السذاجة افتراض أن الشركات الكبرى يحكمها الأخلاق والمباديء ، فهم لن يتوانوا لحظة وبأي وسيلة عن زيادة حصيلتهم ورأس مالهم من المعلومات ، وبمنتهى الفظاظة سيخبرونك أنهم يتجسسون عليك وبراقبونك من أجلك أنت.
لقد أصبحنا قاب قوسين أو أدنى من التنفيذ الإجباري لنظام الخدمات مقابل السمعة والمسمى Social Credit System والمزمع تطبيقه إلزامياً سنة ٢٠٢٠م في الصين ، إذ يتلقى المواطنون خدمات أكثر أو أرخص إن كان لديهم سمعة حسنة بناءاً على نظام إليكتروني لاحتساب النقاط … وطبعاً العكس بالعكس ، فقد يحرم مواطنون من خدمات كاملة أو يحصلون عليها متأخراً أو أغلى ، إن كانت نقاطهم منخفضة … يعكف على بناء هذا النظام مجموعة من أكبر الشركات الرقمية في الصين … والتي تعمل وتطبق هذا في مكان ما ، في السحاب.

قبل أن يطير الدخان

نحن كمبرمجين كثيراً ما نخطيء في تقديراتنا للأمور ، إذ نميل إلى التركيز على تحليلاتنا الفنية المبنية على سنوات طويلة من الخبرة ، ونتجاهل في كثير من الأحيان النظر إلى الصورة العامة ومنها التكاليف والعقبات والعيوب. ربما ذلك راجع إلى ظاهرة أو متلازمة الرؤية النفقية Tunnel Vision التي دائماً ما تصاحب أصحاب الخبرات الفنية الطويلة والذين يتعمقون كثيراً في معرفتهم بمجالهم ، وهذا بالطبع محمود لكنه أيضاً مضر في أوقات كثيرة.

وبالطبع إدارات الشركات تخطيء ، وبالأخص عند عدم استشارة ذوي التخصص ، وبالأخص أكثر المحايدون منهم … وفي النهاية تدفع الشركات أموال طائلة دون مساءلة أحد لأن الجميع يعرفون كيف يلعبون اللعبة وكيف يرصون الأوراق وكيف يمثلون على المستثمرين ، وكيف يتحايلون على القانون بجيوش من المحامين. ونفاجأ يومياً بشركات تعلن إفلاسها ، ويحمي القانون مديرو هذه الشركات من أي ملاحقات قضائية ، ليستمتعوا بممتلكاتهم وثرواتهم وليذهب الجميع إلى الجحيم.

أعلم جيداً فوائد الحوسبة السحابية وكذا عيوبها العديدة … في مقالي هذا حاولت إلقاء بعض الضوء على تلك العيوب ، حتى تتمكن قبل القفز إلى أعلى حيث أحضان السحاب أن تعلم جيداً ما أنت مقبل عليه. لا تشتر أو تستخدم شيء لمجرد السمعة أو للسير مع الاتجاه العام trend … ولا تثق في ما يلقى عليك ، ابحث عن الحقيقة بنفسك واقرأ بين السطور … وابحث عن احتياجاتك الحقيقية وفندها ، حتى تصل في النهاية إلى القرار الأصوب.

Scraping JavaScript-based websites using Selenium and .NET Core

Intro

At work, I needed to find a way to scrape a website and gather data required by some team, and that is quite trivial task IF the website is just a normal one with pages that could be fetched/downloaded using regular HttpClient/WebRequest calls and parse the content via tools like HtmlAgilityPack, but that wasn’t the case, the website was built with Angular, and it makes lots and lots of API calls and then aggregate the data and render them in a meaningful way.

JavaScript-based websites require a different approach for scraping, you need to treat them exactly like an end user does, via browsers, and that exactly what I did.

The other option beside automating this process, is by hiring someone doing all this manually, tinkering with the website DOM and the browser console to retrieve the displayed data, and the hidden info like the items’ IDs. This is quite hectic, so boring, and quite error prone.

So the decision was to automate this, and I already have some good experience with this topic, but that was on .NET Framework WinForms and Node.js with Electron, and I would prefer to build this using the latest .NET Core 2.x, which I’m going to show you in a minute.
Besides that I want to develop on my MacBook machine, using my favourite IDE, Rider, so the solution needs to work cross-platform.

Also, it’s important for me to be able to test my algorithm, is to see the actual browser and the actual behaviour of my code tinkering with the website live.

I started by running some research, and I was disappointed a lot, most of the resources I found were about the old .NET Framework, very little are about .NET Core, and even those are focusing on integration testing rather than scraping, so “headless” browsers are more appreciated there.

In the coming few lines, I will go through each option I found, and the pros and cons of each, and will go deeply over the option I picked.

Option 1: WebBroswer

WebBroswer is a WinForms control that you can use for rendering HTML or just navigating to URLs. It’s basically a wrapper for your local IE, but it has quite aggressive compatibility which makes the rendered results inside it look weird, different, and probably suffer from lots of JS errors, specially if the website is built using quite recent and modern JS framework/library.

For the above reasons, I decided to skip this option and just keep it as my last resort, specially I don’t think that .NET Core WinForms is mature enough, besides of course the other obvious reasons of how bad it is for using IE for browsing and scraping.

Option 2: PhantomJS

PhantomJS is a headless web browser scriptable with JavaScript. It runs on Windows, macOS, Linux, and FreeBSD. Paired with Selenium, you get the job done!

This was my initial choice due to my experience and maturity of the platform, PhantomJS is a quite famous in the field, Selenium and PhantomJS are like the perfect and well known pair for the job.

But, on March 2018, PhantomJS guys decided to suspend further development due to the decrease in contributors and in the community interest, you can read more about this here. This means no more bug fixes, and of course lack of recent browser capabilities support.

Though, I still decided to give it a try. After installing the latest version of Selenium.WebDriver nuget package in a new .NET Core Console application, I found that they stripped out all PhantomJS references, for example the PhantomJSDriver class is not there any more, so I decided to downgrade my Selenium version further down, till I found the classes I was looking for, but with big warnings that those classes are obsolete and are going to be removed in next versions, then I downgraded further, v1.10 to be exact, till I felt that PhantomJS is fully supported by Selenium.

The developing process was straight forward, by initiating PhantomJSDriver by passing the bin location of my OS PhantomJS which I downloaded from https://phantomjs.org/download.html

Since it’s a headless bowser, I had to do lots of trial and error, and the first issue I faced was getting warnings from the target website server that I’m using outdated browser and I need to upgrade to get the best user experience, PhantomJS sends old user-agent information, and that could be overwritten in the code, but that wasn’t my biggest problem.

The biggest issue was reading the nested nodes/elements of a custom HTML component tag, it’s quite normal in Angular application to see tags like <app-root> and <router-outlet>, and those are not really standard HTML tags, and PhantomJS was facing bad times trying to read them and their children.

So the conclusion was a big “No”, sadly enough due to the good times I had with this great tool. It’s time to move on.

Option 3: ChromeDriver

It took me sometime to update my knowledge and learn this new kid. Also I heard about a similar one for FireFox, but I decided Chrome would be a good fit.

The process is very straight forward, you just need to install these two nuget packages, lets say in a new .NET Core Console app:

  • Selenium.WebDriver: latest version
  • Selenium.WebDriver.ChromeDriver: latest version is OK, but you need to check the Chrome version installed on your machine, and you might need to downgrade this one or update your Chrome. This package puts the necessary executable files in your project output folder, so you don’t need to download any custom ones, if you do and want to skip this package, you can still download the target version from http://chromedriver.storage.googleapis.com/index.html

That’s really all you need. After that it’s time for writing some code:

// finding the chrome executable path (bin)
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var driver = new ChromeDriver(path);

driver.Navigate().GoToUrl("https://js-website.com);

// doing some web storage manipulation
driver.ExecuteScript("localStorage.setItem('language', 'en');");
driver.ExecuteScript("localStorage.setItem('lat', '30');");
driver.ExecuteScript("localStorage.setItem('lng', '30');");

// refreshing is important for the website js to handle the updated localStorage
driver.Navigate().Refresh();

// sleeping/throttling waiting for everything to settle
Thread.Sleep(10 * 1000);

var header = driver.FindElementByCssSelector(".navbar-brand");
Console.WriteLine("Header: " + header.GetAttribute("innerHTML"));

var items = driver.FindElementsByCssSelector("div.container span.item");
Console.WriteLine("Items Count: " + items.Count);
foreach (var item in items)
{
    var title = item.Text;
    Console.WriteLine("Item: " + title);
    if (string.IsNullOrWhiteSpace(title) || title == "All Items") continue;

    item.Click();
    Thread.Sleep(5 * 1000);

    // continue...
}

That’s it basically, you can put this code inside your Console Main method, or organize the code the way you like.

This option has been my choice obviously. When I run this code, it initiates a Chrome instance in front of me and I see how my code interacts with the website. Also still you can set some capabilities to make it for example a headless browser, besides some other options specific to Chrome.

Final Notes

  • It’s very important to do throttling between certain actions in your code, giving the website’s JavaScript the time to do its work, it’s quite difficult to detect programmatically when the website’s JavaScript is doing or done with something, that’s why sleeping could be your best option, specially due to inconsistency, lots of JavaScript libraries and non-standard code that you don’t have control over
  • Hitting the same server from the same IP too much, could be flagged automatically by recent servers as a DoS attack, and you might even get your IP blocked, so throttling and slowing down the process and running only through a single thread to resemble a normal human user interaction, would be beneficial
  • Slowing down the process helps me a lot to monitor my code, at least during development/debug times, so the sleeping time could be a setting maybe if you want to publish your scraper
  • You need to have your compliances in check, legal and alike agreements could be needed when you need to scrape others’s websites, respecting their ownership over their data and their conditions. In this specific case I demonstrated, the target website is actually owned the company but the designated team is not working with the company anymore, so it’s much easier just to scrape than running into cycles of discussions and developing and fixing
  • You need to be smart. Maybe instead of writing code for clicking on several elements in a certain order to reach a certain page, you can do the same by just storing something in the localStorage or in a cookie. You need to study your target website and notice it’s behaviours
  • Web scraping requires good experience on both the backend and the frontend, and how the web works and how browsers handle them

That’s it, I hope you enjoy it and hopefully you learn something new, looking forward to your feedback and any questions. Happy coding!

أين اختفت المبرمجات؟

تمهيد

لا يخفى على أحد يعمل في مجال البرمجة خاصة أو تكنولوجيا المعلومات Information Technology عامة ، الانخفاض المستمر في أعداد الإناث في هذا المجال مقارنة بأعداد الذكور … للدرجة التي جعلت الجميع يتصور أن هذه المهنة تناسب الذكور أكثر … وهذه الظاهرة ليست قاصرة فقط على دول بعينها ، ولكنها ظاهرة عالمية يمكن ملاحظتها تقريباً في كل مكان.

في الشركات التي عملت بها سواءً في مصر أو في هولندا ، أو الدول الأخرى التي عملت مع شركاتها عن بعد remotely … يمكنني أن أتنبأ بأن أكثر من ٩٠٪ من العاملين في هذا المجال هم ذكور. ونفس ذلك التقدير تراه جلياً في المؤتمرات العالمية مثل AWS Summit أو Microsoft Ignite أو غيرهم ، فقط ملاحظة سريعة على الحاضرين في أي من المحاضرات التقنية ستعطيك انطباع حقيقي عن حجم الاختلاف الشاسع بين أعداد الذكور والإناث.

يدخل أيضاً في هذه الملاحظة أعداد المنسحبات من هذه المهنة … لقد عملت في مرات كثيرة مع مبرمجين ذكور أعمارهم أكبر من ٤٥ عاماً ، لكن لم أقابل أي مبرمجة أنثى تخطت ال٤٠ ، لا في مصر ولا خارجها … وقد لاحظت أيضاً أن الكثيرات يفضلن تغيير مسار عملهن إلى مسارات أخرى قد تكون قريبة نوعاً ما مثل Scrum Master أو بعيدة كل البعد مثل المحاسبة!

أنا هنا لا أتحدث عن صناعة البرمجيات ككل ، ولكن أخص حرفة البرمجة ومن يمتهنونها ، المبرمجين اللذين يدخل كتابة الكود جزءاً أساسياً من يومهم ومسؤولياتهم الوظيفية ، أولئك الغارقون في التأمل يبحثون عن عالم أفضل. هؤلاء ، وأنا منهم ، هم من أكتب عنهم.

مناقشات

أجريت عدة مناقشات مع زملاء الكار والعاملين في المجال للوقوف على ملاحظاتهم حول هذا الشأن ، وقد تباينت الأراء كثيراً وفقاً للخلفيات الاجتماعية والثقافية وكذلك الدينية ، فكل تقريباً الزملاء من الشرق الأوسط يرى أن ذلك راجع إلى الاختلافات الطبيعية والتشريحية والجينية بين الذكور والإناث ، فالذكور لديهم قدرة أعلى على العمل تحت الضغط ولفترات طويلة قد تصل لأسابيع متصلة مع مواصلة الليل بالنهار ، وهذه الأخيرة تحديداً كانت سبباً رئيسياً لكثير من أصحاب العمل ولا سيما الناشئة منها startups لتفضيل الذكور على الإناث.

وأيضاً يرى هؤلاء أن فرق العمل تكون في حالة انسجام أكثر عندما تكون كلها من الذكور ، فمثلاً يمكن في حالات الضرورة الاحتدام في المناقشات الفنية ، وهو ما لا يمكن حدوثه في وجود إناث لأنهن سيشرعن في البكاء عند علو الأصوات وتوجيه الاتهامات. ربما هذا أيضاً يدفع الإناث بعيدا عن هذا المجال كرههن للمنافسات والتحديات “الذكورية” Alpha-male أو على الأقل منافسات الأفضلية في الفريق ومن الأكثر اطلاعاً ومتابعة للجديد في هذا المجال سريع التغير.

أما زملاء الغرب وتحديداً هولندا ، فإنهم يستشعرون الحرج من مناقشة هذا الموضوع ، ربما لا يريدون أن يظهروا كعنصريين ضد الجنس الآخر sexist ، أو لعدم توافر المعرفة الكافية بالموضوع وأبعاده … لكنهم بلا شك يدركون حجم البين وكذا يرون أنه يجب تشجيع الإناث ولا سيما صغاراً على الخوض في تعلم البرمجة واحترافها وامتهانها.

سابق

كان رأيي الشخصي ، ربما راجعاً لنشأتي ، يتفق إلى حد كبير مع رأي زملاء الوطن … فكنت دائماً أرى أن الإناث يحتجن إلى معاملة خاصة داخل الفريق والشركة ، ولا سيما مع تغير الهرومونات والمود مداً وجذراً خلال الشهر ، وما لهذا من تأثير على الجميع … الغريب أني لم ألاحظ ذلك كثيراً هنا في هولندا ، ربما تنشئة الإناث في الشرق الأوسط له يد في ذلك.

وكنت أيضاً أرى أن الإناث ليس لديهن القدرة والطاقة العقلية للعمل على المهام شديدة الصعوبة والتعقيد ، لا أقصد أبداً هنا أن الذكور أذكى من الإناث ، مطلقاً هذا ليس رأيي ، ولكن فقط في الحالات الذهنية الصعبة والمعقدة والطويلة ، تكون للذكور الأفضلية … وكان رأيي أيضاً أن هذا أهم أسباب انسحاب الإناث من هذا المجال ، فهن يفضلن ، وكذا كثير من الذكور ، في العمل في مهن ذات تحديات أقل وذات روتين وإيقاع ثابت ، ولا تتطلب القراءة والمذاكرة والاطلاع المستمر في المجال والتجديد والتحديث إلخ.

ويتفق مع رأيي هذا “السيدة” الدكتورة في علم النفس العصبي Louann Brizendine … في كتابها The Female Brain ، تقول أن الجزء من المخ المسؤول عن الاستماع والاستقبال والتذكر والكلام ، يكون أكبر حجماً في الإناث بمعدل الضعف تقريباً عن الذكور ، وهذا يفسر قدرتهن على التذكر الجيد وكذا سماع طبقات صوتية تمكنهن مثلاً من إدراك حاجة الرضيع للنظافة أو الأكل دون أن يتكلم ، وأيضاً قدرتهن على الكلام وتكوين علاقات اجتماعية أفضل من الذكور ومالهذا من أهمية في بناء المجتمعات ، ولهذا الأخير فالإناث لا يفضلن الأعمال التي تطلب عزلة طويلة بعيداً عن تواصل بشري مثلما يحدث في البرمجة في بعض الأوقات.

أما الذكور عكس الإناث ، فالدكتورة تقول أن الجزء من المخ المسؤول عن التحليل والتخطيط ولاسيما الاستراتيجي طويل الأمد ، يكون هذا الجزء من المخ أكبر في الذكور بمعدل الضعف تقريباً ، وهو ما يفسر قدرة الذكور في العمل على مهام مجهدة عصبياً وذهنياً لفترات طويلة ، لا سيما تلك التي تتطلب تفكير عقلاني منطقي فقط دون الخوض في المشاعر.

لاحق

رأيي السابق كنت أحتفظ به لنفسي معظم الوقت ، من ناحية حتى لا أتسبب في أحتقانات وجدالات أنا في غنى عنها ولا طائل من ورائها ، ومن ناحية أخرى لأني ما زلت غير مقتنع ١٠٠٪ بهذه الأسباب … إذن قررت ترك باب البحث مفتوحاً حتى يتضح لي المزيد.

لم أندهش عندما وجدت أن رأيي السابق يشاطرني فيه الكثيرون ممن تعرض منهم لهذه المسألة فيما يخص مهنة البرمجة … هذا الرأي له وجاهته وقوته ولكنه لم يتمكن من الإجابة على العديد من التساؤلات المشروعة:

  • إن كانت الاختلافات الطبيعية والجينية بين الجنسين هي السبب في ندرة أعداد المبرمجات ، فماذا يفسر إذاً أعداد المبرمجات في دول مثل الهند حيث يبلغن في أماكن كثيرة ٥٠٪ من المبرمجين؟
  • في فترة الخمسينات والستينات ، كان الاعتقاد السائد أن البرمجة مهنة للإناث … كانت البرمجة وقتها صعبة فلا لوحات ولا شاشات ، فقط شرائط ورقية مثقوبة يتم إدخالها في الحاسبات العملاقة المعزولة في غرف مكيفة ، وعند حدوث خطأ ، على المبرمجة أن تقرأ التعليمات البرمجية وتتوقع بنفسها السبب ، ثم تعيد العملية مرة تلو الأخرى … الإناث كن يقمن بهذا وأكثر ، وبعضهن لهن فضل على البرمجة بل البشرية جمعاء. الجينات لا يمكنها تفسير هذا التغير بين الأجيال.
  • في السنوات الأخيرة قام العديد من الباحثين بأبحاث أكثر تعقيداً على المخ البشري والاختلافات فيه بين الجنسين ، ولا سيما مع تطور العلم وأدواته … خلص الكثيرون نتيجة هذه الأبحاث أن تطور المخ البشري يخضع بشكل كبير للعوامل الاجتماعية والمتطلبات الفردية ، فالمخ يمكنه التكيف والتطور بأشكال متعددة حسب الحاجة ، ليس فقط وفق جنس الإنسان.

وبالإضافة لما سلف ، فالإجابة بأن الجينات هي السبب تعتبر إجابة كسولة سهلة ، تغلق الباب أمام أي نقاشات أو أبحاث … وهو عكس ما يحتاجه هذا الموضوع ، إذ يجب تسليط الأضواء أكثر عليه ودراسته من جوانبه المختلفة.

الكاتب والصحفي Clive Thompson في كتابه الشيق القيم Coders ، ناقش في جزء كبير منه هذا الموضوع … ولمح أن النشأة الاجتماعية لها دور مهم ، فبلدان شرق ووسط أسيا مثل الهند يشجعون بناتهن على الخوض في هذا المجال ، والمجتمع ككل يتقبل هذا بشكل جيد ، على عكس مجتمعات أخرى كثيرة مثل المجتمع الأمريكي ، حيث يحصل الأولاد في أعياد ميلادهم على كمبيوترات بينما تحصل الفتيات على عرائس ، هذا غير نظرة الأقران السيئة بين البنات تجاه المهووسات بالبرمجة والتكنولوجيا geeks ، وغيرها من فروقات جنسية اجتماعية.

وفي مجتمعاتنا الشرقية ، يتم تنشئة البنات أن مآلهن إلى بيت الزوجية وأن هذا هو دورهن وهدفهن في الحياة ، غير الكثير من الأعراف والتقاليد الأخرى … وكل هذا بالطبع يؤثر على تطور شخص وعقل الأنثى واختياراتها وادراكها الواعي واللاواعي ومن ثم قراراتها في الحياة.

الشركات التي تفضل الذكور لأن لديهم قابلية أكبر على العمل لساعات أطول ، وأحيانا كثيرة ما تفتعله من ضغط عمل مستمر وما يستتبعه ذلك من اختلال في توازن الحياة والعمل لدى الموظفين ، هذه الشركات سبب هام لهذه المشكلة التي نحن بصددها الآن.

هذه الشركات في الحقيقة بيئات عمل غير مستدامة unsustainable ، وسيحدث إن آجلاً أو عاجلاً هروب العمالة من هذه الشركات ولا سيما العمالة الماهرة وفي القطاعات ذات المرتبات المرتفعة مثل البرمجة ، ومن ثم خسارة هذه الشركات وخروجها من السوق تباعاً.

الخلاصة

الموضوع جل وكبير ، حاولت قدر استطاعتي تلخيصه وتسليط الضوء على أهم جوانبه وأبعاده … ومع اجتهادي فيه وحرصي ، يبقى احتمال خطأي قائماً ، فكلي ترحاب بمن يخالفني أو بمن يريد تعليمي وتصويبي.

وتبق الإجابة على السؤال: “أين ذهبت المبرمجات؟” مرهونة بالمجتمع والأسرة والفرد ، وكلي خوف أن تكون أسئلة أخرى من هذا النوع مطروحة بالفعل دون إجابات أو تصحيح … أعتقد أن كافة المجالات بشكل وبأخر تستحق دراسة مشابهة ، ولكن هذا مجالي وهو ما أعرفه.

ادعموهم وشجعوهم … أو على الأقل دعوهن يخترن ، فقط قفوا على الحياد وتأملوا.

#include
Scott Hanselman

أول مقال أكتبه منذ سنوات

هذا أول مقال أكتبه منذ فترة طويلة ، وقد ترددت كثيرا في كتابته ولا سيما مع اختياري للغتي الأم ، العربية ، لأكتب بها. كانت دائماً ما تكون كتاباتي باللغة الانجليزية بحكم عملي وتخصصي.

أنا محمد قناوي ، مصري مقيم بهولندا منذ ٢٠١٦ ، أعمل مبرمجاً منذ ٢٠٠٦ ، أحب القراءة والفيديو جيمز ، أب وزوج ، أكثر ما يستهويني للقراءة هي التاريخ ، وعلم النفس.

فمنذ صغري ولدي شغف كبير بعلم النفس ومراقبة البشر وتحليل تصرفاتهم ، ولا سيما في بيئة العمل. ومع قراءاتي الكثيرة والاطلاع المستمر مع سنوات العمر ، وجدت أنه قد حان الوقت لأستفيض فيما تجمع لدي من علم وأكتبه هنا ، ناقلاً خبرتي وملاحظاتي حول كافة شؤون الحياة.

بسم الله نبدأ.