2. Fractal processing server example - part 1
Contents
Today I want to discuss a simple example for a notification server, which is often given as a first exercise for developers new to the framework. This will be a variation of the usual exercise, so I can demonstrate some of the core concepts. The notification server we want to implement today is a server that generates pictures of fractals. For those of you who are not familiar with fractals, these are mathematical shapes with self-symmetry. Some classic examples are Koch’s snowflake and Sierpinski’s triangle. We will be interested in more complicated fractals that are produced by using complex numbers and give rise to pretty pictures. The fractals our server will be producing are Mandelbrot and Julia sets.
Our notification server will receive requests for fractal images. These requests will contain parameters relevant to the computations needed to be performed. Each request will also contain the width and height of the produced image and a file path to which the produced image should be saved.
The way we create a fractal image is using the following idea: we translate each pixel of the image to some point in the plane. We think about this point as a complex number. Then we run some iterative process on the complex number, which results with an integer that represents how many iterations of the process were done. We convert this integer into an RGB color and use that for the color of our pixel.
There are many ways to model this as a notification server, and I will show only one. The proposed structure is as follows:
Let us describe each component in this diagram:
- RequestListener - some listener that produces events when a user requests an image. We do not specify how this happens: for instance, changes to a folder in the file system might trigger this to happen. Alternatively, an HTTP request might trigger this. The produced events contain data regarding the computation needed to be performed. They also include the dimensions of the output image (its width and height in pixels) and the file path to which the produced image should be saved.
- PixelGeneratorLogic - receives requests from the listener. For each such request, and for each pixel of the image, it produces as an event the pixel. Every pixel event keeps track of the request event as one of its properties. The request event will be used in the next logics and is necessary in order to make it possible later to relate the pixel to the request.
- PixelToComplexConvertLogic – receives a pixel event from the above described logic. Converts this pixel to a complex number, based on the pixel’s coordinates and based on the request event’s parameters. Produces an event that contains the pixel information and contains in addition the computed complex number.
- MandelbrotLogic/JuliaLogic - receives an event of a pixel with its computed complex number. Runs an iterative computation which eventually terminates. This computation uses parameters specified in the request event. Produces an event containing the pixel information and the number of iterations performed in the computation.
- ColorConvertLogic - receives an event of a pixel with the number of iterations performed on it. Converts the number of iterations to an RGB color. Produces an event with the pixel information and the computed color.
- PixelStoreLogic - receives request events from the listener. For each such event, allocates an event representing the computed image, is kept in this logic’s memory. It also receives events of pixels with computed colors from the ColorConvertLogic. Each such pixel is stored in the big event representing the request’s computed image mentioned before. When all pixels are received, this logic publishes the request’s complete computed image event, and clears this event from its storage. The produced event also contains the request information.
- BitmapConvertLogic - receives completed image events, and produces an event containing a bitmap object representing the image and containing the request details.
- FileSystemDispatcher - receives an event with a bitmap object, and saves it to the file system, according to the path specified in the request.
I want to make some remarks: A core principle of the notification server architecture is that components do not know each other directly. They receive events and produce events, but that is the only way they can interact. They do not have access to each other’s class instance, they don’t know the class type of one another, etc. This allows one to change a component implementation, without having to change the components linking or linked to it. One might even split a component into a chain of multiple components, and this will work as long as the chain receives as input and produces as output the same input and output as the original component.
Another thing is that many of the components here are stateless: they do not maintain any state, they just run some computations on the received events. These include all components except for the PixelStoreLogic, as it stores the computed pixels until all of them have been computed. The listener and the dispatcher are also not exactly stateless: they do not maintain any data, but they do interact with external resources, such as the file system, and therefore they are not considered strictly stateless. Stateless components are important because they scale.
A final remark is that both MandelbrotLogic and JuliaLogic are connected to the PixelToComplexConvertLogic. But we are not interested in performing both computations to all pixels. The computation we choose should be based on a parameter in the request event. In order to address this, the notification server introduces a concept called a rule. A rule is a mechanism that filters events according to, well, some rule. The notification server allows us to specify a rule for each link between components. In our case, we can put a rule that filters only Mandelbrot request types between the MandelbrotLogic and the PixelToComplexConvertLogic, and a similar rule for the JuliaLogic.
Next time we will explore how a (partial) implementation of this example should look like codewise. This will allow us to discuss the code structure of the notification server.
Author זלינגר
LastMod 2020-07-15