
Connexus Observer (Part 8): Adding a Request Observer
Introduction
This article is the continuation of a series of articles where we will build a library called Connexus. In the first article, we understood the basic operation of the WebRequest function, understanding each of its parameters and also created an example code that demonstrates the use of this function and its difficulties. In the last article, we created the client layer, a simple and intuitive class responsible for sending requests, receiving a request object (CHttpRequest) and returning a response (CHttpResponse) that contains information about the request, such as status code, duration, body and response header. We also created a decoupling of the class with the WebRequest function, making the library more flexible, creating a new layer called CHttpTransport.
In this eighth article of the series, we will understand and implement an Observer in the library, to facilitate the management of multiple requests by the client. Let's go!
Just to remind you of the current state of the library, here is the current diagram:
What is an Observer?
Imagine the Observer as that friend who, half hidden, just watches from afar, paying attention to everything, but without interfering. In the programming world, the Observer pattern does something very similar: it allows certain objects to be "notified" when something changes, but without needing to know exactly who caused the change. It's almost like a magic touch: someone moves a part and whoever needs it, at the time, already knows. This pattern is one of the classics for keeping the gears of business logic and the interface working side by side. This way, the system gains that air of fluidity, with several parts automatically adjusting to events.
The idea was born to solve one of those annoying problems in very rigid systems, where one object depends on another and is "stuck" to it, without much room to breathe. The solution? Decouple, make it lighter. Before the Observer gained a name and surname, programmers were already looking to make systems lighter. It was then that, back in 1994, **Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides** in their work Design Patterns: Elements of Reusable Object-Oriented Software (1994) put forward the Observer as the ideal way to keep multiple objects up to date with changes to a single object, without the chains of a strong binding.
Why Use the Observer Pattern?
The Observer is perfect for when we need decoupling, when we want to make things more independent from each other. The Subject doesn't need to know who is watching, it just needs to shout "change in sight!" and move on. It can also be useful for real-time updates**,** systems that need to update instantly, such as interactive interfaces or automatic notifications, are much more agile with the Observer.
Components of the Observer Pattern
- Subject: This is the "owner of the piece" whose state changes and that needs to notify the observers about these changes. It keeps a list of Observers and has methods to add or remove someone from the list.
- Observer: Each Observer is like a "listener", always ready to react to the Subject's changes. It implements an update method that the Subject calls every time a change occurs.
I'll add a diagram below that shows how the observer pattern works:
- Main Classes
- Subject: This class maintains a collection of observers ( observerCollection ) and provides methods to manage these observers. Its function is to notify observers whenever a state change occurs.
- Methods:
- registerObserver(observer) : Adds an observer to the collection.
- unregisterObserver(observer) : Removes an observer from the collection.
- notifyObservers() : Notifies all observers by calling the update() method of each observer in the observerCollection .
- Methods:
- Observer: This is an interface or abstract class that defines the update() method. All concrete observer classes (concretions of Observer ) must implement this method, which is called when the Subject notifies changes.
- Subject: This class maintains a collection of observers ( observerCollection ) and provides methods to manage these observers. Its function is to notify observers whenever a state change occurs.
- Concrete Classes
- ConcreteObserverA and ConcreteObserverB: These are concrete implementations of the Observer interface. Each implements the update() method, which defines the specific response to a change in the Subject .
- Relationship between Subject and Observer
- The Subject maintains a list of Observers and notifies them by calling observer.update() for each observer in the collection.
- The concrete Observers react to changes that occur in the Subject according to their specific implementation in the update() method.
How will this be useful in the Connexus library? We will use this pattern to inform the client code when a request was sent, when a response was received, or even when an unexpected error was generated. Using this pattern, the client will be informed that this happened. This makes it easier to use the library, because it avoids creating conditions in the code such as, for example, “if an error was generated, do this”, “if a request was made, then do this”, “if a response was received, then do this”.
Hands on code
First I'll demonstrate a diagram that shows how we're going to add this pattern in the context of the library:
Let's understand better how the implementation will be
- Note that the diagram has the same structure as the reference diagram
- I added two methods that the observers will have access to:
- OnSend() → When a request is sent.
- OnRecv() → When a response is obtained.
- IHttpObserver will not be an abstract class, but rather an interface.
Creating the IHttpClient interface
First we create the IHttpClient interface at the path <Connexus/Interface/IHttpClient.mqh> . And we define the two notification functions
//+------------------------------------------------------------------+ //| IHttpObserver.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "../Core/HttpRequest.mqh" #include "../Core/HttpResponse.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ interface IHttpObserver { void OnSend(CHttpRequest *request); void OnRecv(CHttpResponse *response); }; //+------------------------------------------------------------------+
Creating the list of observers in CHttpClient
Let's add the interface import.
#include "../Interface/IHttpObserver.mqh"Now let's create an array of observers in the private field of the class. Remember that this array should store pointers, so we need to add the “*” before the variable name. We will also create public methods for adding, removing and notifying all observers that are stored in the array.
//+------------------------------------------------------------------+ //| HttpClient.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "HttpRequest.mqh" #include "HttpResponse.mqh" #include "../Constants/HttpMethod.mqh" #include "../Interface/IHttpTransport.mqh" #include "../Interface/IHttpObserver.mqh" #include "HttpTransport.mqh" //+------------------------------------------------------------------+ //| class : CHttpClient | //| | //| [PROPERTY] | //| Name : CHttpClient | //| Heritage : No heritage | //| Description : Class responsible for linking the request and | //| response object with the transport layer. | //| | //+------------------------------------------------------------------+ class CHttpClient { private: IHttpObserver *m_observers[]; // Array of observers public: //--- Observers void RegisterObserver(IHttpObserver *observer); void UnregisterObserver(IHttpObserver *observer); void OnSendNotifyObservers(CHttpRequest *request); void OnRecvNotifyObservers(CHttpResponse *response); }; //+------------------------------------------------------------------+ //| Add observer pointer to observer list | //+------------------------------------------------------------------+ void CHttpClient::RegisterObserver(IHttpObserver *observer) { int size = ArraySize(m_observers); ArrayResize(m_observers,size+1); m_observers[size] = observer; } //+------------------------------------------------------------------+ //| Remove observer pointer to observer list | //+------------------------------------------------------------------+ void CHttpClient::UnregisterObserver(IHttpObserver *observer) { int size = ArraySize(m_observers); for(int i=0;i<size;i++) { if(GetPointer(m_observers[i]) == GetPointer(observer)) { ArrayRemove(m_observers,i,1); break; } } } //+------------------------------------------------------------------+ //| Notifies observers that a request has been made | //+------------------------------------------------------------------+ void CHttpClient::OnSendNotifyObservers(CHttpRequest *request) { int size = ArraySize(m_observers); for(int i=0;i<size;i++) { m_observers[i].OnSend(request); } } //+------------------------------------------------------------------+ //| Notifies observers that a response has been received | //+------------------------------------------------------------------+ void CHttpClient::OnRecvNotifyObservers(CHttpResponse *response) { int size = ArraySize(m_observers); for(int i=0;i<size;i++) { m_observers[i].OnRecv(response); } } //+------------------------------------------------------------------+Finally, we call the notification function inside the function that sends the request so that the observers are actually informed:
//+------------------------------------------------------------------+ //| class : CHttpClient | //| | //| [PROPERTY] | //| Name : CHttpClient | //| Heritage : No heritage | //| Description : Class responsible for linking the request and | //| response object with the transport layer. | //| | //+------------------------------------------------------------------+ class CHttpClient { private: IHttpObserver *m_observers[]; // Array of observers public: //--- Basis function bool Send(CHttpRequest &request, CHttpResponse &response); }; //+------------------------------------------------------------------+ //| Basis function | //+------------------------------------------------------------------+ bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response) { //--- Request uchar body_request[]; request.Body().GetAsBinary(body_request); //--- Response uchar body_response[]; string headers_response; //--- Notify observer of request this.OnSendNotifyObservers(GetPointer(request)); //--- Send ulong start = GetMicrosecondCount(); int status_code = m_transport.Request(request.Method().GetMethodDescription(),request.Url().FullUrl(),request.Header().Serialize(),request.Timeout(),body_request,body_response,headers_response); ulong end = GetMicrosecondCount(); //--- Notify observer of response this.OnRecvNotifyObservers(GetPointer(response)); //--- Add data in Response response.Clear(); response.Duration((end-start)/1000); response.StatusCode() = status_code; response.Body().AddBinary(body_response); response.Header().Parse(headers_response); //--- Return is success return(response.StatusCode().IsSuccess()); } //+------------------------------------------------------------------+
Work done, and simpler than it seems when we write the code, isn't it? With this, we have completed the entire implementation within the library. We need to create the observers, that is, the concrete classes that implement the IHttpObserver. We will do this in the next topic, the tests.
Tests
Now all we need to do is use the library. To do this, I will create a new test file called TestObserver.mq5, in the pat <Experts/Connexus/Tests/TestObserver.mq5>. We will import the library and leave only the OnInit() event.
//+------------------------------------------------------------------+ //| TestObserver.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Just below the import I will create a concrete class that implements the IHttpClient interface, it will just print on the terminal console the data that was sent and received using the library:
//+------------------------------------------------------------------+ //| TestObserver.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CMyObserver : public IHttpObserver { public: CMyObserver(void); ~CMyObserver(void); void OnSend(CHttpRequest *request); void OnRecv(CHttpResponse *response); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::CMyObserver(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::~CMyObserver(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::OnSend(CHttpRequest *request) { Print("-----------------------------------------------"); Print("Order sent notification received in CMyObserver"); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::OnRecv(CHttpResponse *response) { Print("-----------------------------------------------"); Print("Response notification received in CMyObserver"); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create objects CHttpRequest request; CHttpResponse response; CHttpClient client; CMyObserver *my_observer = new CMyObserver(); //--- Configure request request.Method() = HTTP_METHOD_GET; request.Url().Parse("https://httpbin.org/get"); //--- Adding observer client.RegisterObserver(my_observer); //--- Send client.Send(request,response); //--- Delete pointer delete my_observer; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
When running this on the graph we get this result:
This shows that the functions of the CMyObserver class were called within the library, this changes everything, we complete the library with the main objective accomplished, that it be flexible.
The most interesting part is that we can have several observers in different parts of the code, if we have an EA that is divided into several classes, we can have each of these classes create an implementation of IHttpObserver and that's it! We will be notified as soon as the request is sent or a response is received.
Now with these observer inclusions this is the current diagram of the library:
Refactoring the folders
Currently the directory of all the library files looks like this:
|--- Connexus |--- |--- Constants |--- |--- |--- HttpMethod.mqh |--- |--- |--- HttpStatusCode.mqh |--- |--- Core |--- |--- |--- HttpClient.mqh |--- |--- |--- HttpRequest.mqh |--- |--- |--- HttpResponse.mqh |--- |--- |--- HttpTransport.mqh |--- |--- Data |--- |--- |--- Json.mqh |--- |--- Header |--- |--- |--- HttpBody.mqh |--- |--- |--- HttpHeader.mqh |--- |--- Interface |--- |--- |--- IHttpObserver.mqh |--- |--- |--- IHttpTransport.mqh |--- |--- URL |--- |--- |--- QueryParam.mqh |--- |--- |--- URL.mqh
We'll make two adjustments: include the files from the URL folder in the Data folder, and rename them to Utils, thus simplifying both folders that contain files with similar purposes. We'll also add the interfaces folder inside the Core folder, since the interfaces are part of the core of the library. In the end, the library's folder structure looks like this:
|--- Connexus |--- |--- Constants |--- |--- |--- HttpMethod.mqh |--- |--- |--- HttpStatusCode.mqh |--- |--- Core |--- |--- |--- Interface |--- |--- |--- |--- IHttpObserver.mqh |--- |--- |--- |--- IHttpTransport.mqh |--- |--- |--- HttpClient.mqh |--- |--- |--- HttpRequest.mqh |--- |--- |--- HttpResponse.mqh |--- |--- |--- HttpTransport.mqh |--- |--- Utils |--- |--- |--- Json.mqh |--- |--- |--- QueryParam.mqh |--- |--- |--- URL.mqh |--- |--- Header |--- |--- |--- HttpBody.mqh |--- |--- |--- HttpHeader.mqh
Renaming some methods
When it comes to writing code that is easy to understand, maintain, and improve, adopting a standard coding style makes all the difference. Having a consistent standard when creating libraries goes far beyond simple aesthetics; it brings clarity, predictability, and a solid foundation for anyone who will use or collaborate with the code, today or in the future. This uniform style is not just a matter of organization; it is an investment in quality, robustness, and healthy growth of the library over time. And even though it may seem like just a detail at first, it ends up being the guiding thread that makes the code safe and more prepared to evolve.
Why is having a Standard Style Essential?
- Consistency and Readability: Well-structured code with a uniform style makes reading more fluid and understandable for any developer. With a well-defined standard, people don't need to waste time deciphering variations or inconsistencies; instead, they can focus on what really matters: the logic of the code. Aspects such as spacing, indentation, and naming are details that, together, create a more intuitive and straightforward experience. Everything is aligned, facilitating navigation and reducing the stumbling blocks caused by varied and disconnected styles.
- Ease of Maintenance and Expansion: Libraries rarely stand still in time; new challenges arise and it is natural that they need adjustments. With a standardized coding style, maintenance becomes simpler and less prone to errors. This not only saves time when fixing problems, but also makes it easier for new developers to quickly understand the code and collaborate efficiently. And, of course, a library that is well-structured from the beginning is much easier to scale, since each new feature finds a predictable and organized environment in which to integrate.
That said, let's define some standards in the code, mainly in the naming of functions. Some other standards have already been applied, such as:
- All classes use the prefix “ C ” before the name
- All interfaces use the prefix “ I ” before the name
- Private variables use the prefix “ m_ ”
- Methods must always start with capital letters
- ENUM values must be written in uppercase
All these standards have already been applied in the library during development, but we will add others, such as:
- Methods to set/get attributes of a class must use the prefix Get or Set
- Methods that return the size of the array must be named “ Size ”
- Methods that reset the class attributes must be named “ Clear ”
- Methods to convert to string must be named “ ToString ”
- Avoid name redundancy in context classes. For example, the CQueryParam class has the AddParam() method, which doesn't make sense. The ideal would be just Add() since we are already in the context of parameters.
That said, I will not list all the methods in the library that I will rename, nor will I provide the source code, since I am not changing the implementation of the method, only the name. But with the changes, I will leave a diagram below that shows all the classes in the library with the names of the updated methods and their relationships.
Conclusion
With this last article, we conclude the series on the creation of the Connexus library, designed to simplify HTTP communication. It was quite a journey: we went through the basics, delved into more advanced design and code refinement techniques, and explored the Observer pattern to give Connexus the reactivity that is essential in dynamic and flexible systems. We implemented this pattern in practice, so that various parts of the application can automatically react to changes, creating a robust and adaptable structure.
In addition to the Observer, we organized the entire file and folder architecture, making the code modular and intuitive. We also renamed methods to increase clarity and make the use of the library more direct and consistent, details that make all the difference when it comes to clean code and long-term maintenance.
Connexus was designed to make HTTP integration as simple and intuitive as possible, and we hope that this series has shown each important point in the process, revealing the design choices that made this possible. With this final article, I hope that Connexus not only simplifies your HTTP integrations, but also inspires continuous improvements. Thank you for embarking on this journey with me, may Connexus be an ally in your projects!





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use