Показаны сообщения с ярлыком architecture. Показать все сообщения
Показаны сообщения с ярлыком architecture. Показать все сообщения

воскресенье, 18 января 2015 г.

Simple architecture harder than complex

I want to write about the architecture of my latest commercial software project I developed (http://iseepro.ru/) and I want to do this in storytelling manner. The reason I chose a narrative style is that it is not the architecture itself that is valuable (there is nothing special about it as you going to see later), but the way we got to it, the decisions and considerations that led us to where we currently are. We were restricted on time and on budget (we had none of both), but nevertheless managed to come up with the best product available within the market (this is very subjective, until proven by others, forgive me). This is a story of simplifying everything to the very extreme.


Before I start, please check out statistics. The infamous Chaos Report published by Standish Group in 1995 revealed horrible numbers - 84% of IT projects considered to be a failure. The experiment involved managers of all levels from several thousand companies throughout all industries involved in IT activities. Well, 1995 is almost Middle Ages, we can only feel sorry for IT dinosaurs. Technologies made a tremendous progress with time both in complexity of tasks solved and in easiness of adoption. We’d expect to have drastically different numbers nowadays, right? IBM have published results for a similar survey conducted in 2008. The numbers were not that promising either - 60% of projects failed in that or another way. Imagine, with .NET 3.5, Java 6, RoR 2.x, with all that goodness we failed almost every second project. Seems like the problem is not the technology alone.  The main reason why projects fail is the lack of focus (McKinsey, link). It is the lack of focus on business problem that leads to over-complicated solutions in a desperate rush for superfluous qualities. This is why we break the schedules and get out of budget.


Having this in our minds we started our Cash Control system development.
Cash Control systems are used to mitigate money loss risk at points of sale. They help fight internal and external fraud in retail.
The appliance is made of a CCTV system integrated with a set of analytical tools operating on devices’ event streams.
The system is relatively simple and functional requirements can be captured in these three simple bullet points. The system should be able to:
  • mix and correlate video stream with device event stream, both in real-time and afterwards;
  • match risky behaviour patterns in device event flow in real-time with customizable templates;
  • provide post-analysis tools and generate reports by cashdesks, cashiers, goods and alerts found.


Non-functional requirements can be captured with a relatively short list as well. System should be:
  • open for integration with devices of different types:
    • cashdesks
    • petrol stations, etc.
  • open for integration with different CCTV systems. Even though our default package includes our partner’s system, our client may have already invested in another CCTV  appliance. In this case we provide them with analytics tool package, integrating it with CCTV system of their choice.
  • cross-platform;
  • scalable, as our clients may be as small as one shop with several cashdesks and as huge as a network of hypermarkets.


We set up our first goal to launch the system in a big hypermarket in Moscow in half a year. Please bear in mind that none of us left their primary occupation.


Can you imagine, how happy my inner architecture astronaut became, as he met this kind of challenge? Immediately, without a single moment of thinking, I got a message from stars with the overall system design. Nice, isn’t it? So here is the ideal world (or the world with lack of oxygen, it depends on how you see it) picture.





This solution is so fucking universal that it could install and administer itself, if only my inner architecture astronaut stayed without oxygen a little longer. It can serve any client, no matter how big it is. Could it be another way if you have SPA, nginx, Thin, Cramp, node.js, NoSql, Mapreduce, amqp, RabbitMQ?!


A couple of years ago I had a joy of attending QCon London. I spent two days in a row at my favourite real-life projects track. Should I mention, that by the end of the second day I had already had enough of MySQL to Postgres and vice versa epic changes? The last talk was performed by ex-googlers Triposo founders. And believe it or not, but I was rewarded for my patience by these guys. Their attitude seemed so different from others’. They didn’t bother building super reliable infrastructure, but used existing available infrastructure instead: they kept their knowledge base backups in Dropbox; had static content stored and served by Amazon S3; put dynamic settings to change applications’ behaviour into a Google docs spreadsheet. Em.. Whoat?! Google docs as a part of a system?! Believe it or not, but ex-googlers didn’t create a clusterized parallelized distributed highly available redundant infrastructure. They chose not to do it at all. Their capital expenses were huge enough to allow themselves two workstations under a kitchen table. That’s where all magic happens - these two stations generate and publish travel guides.
So what’s so special about these guys? They managed to build a successful business, because they had focused on their competitive advantage - extensible knowledge base, automatic guide builder and UX.


Our competitive advantage to be focused on is the most convenient analytics toolset which allows to solve our clients’ real life issues. Our major clients are retail market networks. Our first playground is a hypermarket in Moscow. Facts are:
  1. Real-time monitoring performed by guards is not the main use case by any means. The main scenario is alert post-analysis performed by a security analyst.
  2. Load is not evenly distributed throughout the day and has a certain profile. It grows slowly from morning till evening and reaches its peak at rush hours. It is almost flat around zero during nighttime. We may get a load increased by a factor of two or three at days before holidays and weekends.
  3. Retail infrastructure is mostly built on Windows;
  4. Our software delivered on-premise.


Data-layer.
There is a NoSql database drawn by my inner architecture-astronaut. I salute this choice, in general, as long as it at least shows that you have thought about your data and the best way to store that. But this is less and less true nowadays.
But why NoSQL? First of all, it is data volume. We’ve examined a cashdesk communication protocol and estimated our hypermarket to produce around 600К events daily or 200М annually.  With average message size balancing around 1 KB, it totals to ~600 MB daily, or ~200 GB annually for just one hypermarket. However, we should remember that several hypermarkets  united in a single network may generate an order of magnitude more events. NoSQL works well with huge amounts of non-relational data, as long as this data is easily distributable due to its nature. The second reason to opt for NoSQL is write performance. It is obvious, that write operations will prevail read operations in an aggregating system. NoSQL has a good write performance as long as these operations are as easy to distribute as it is easy to partition the data itself. And the last, but probably the most important reason to chose NoSQL, is the lack of schema. Initially we would prefer not to restrict a message schema as long as different protocols have different fields in messages. There are few messages with identical set of fields even within one protocol.
Considering the fact that we were going to need elaborate search instruments our best bet here would have been a document-oriented database, like MongoDB. To accommodate for a huge dataset we would have several instances holding several shards in a cluster, of course.
However, as I’ve already noticed, we were going to deploy our product on-premise and we had to take into account unskilled personnel. They can handle routine tasks like a database backup without problem, yet database cluster support is far beyond their capabilities. That would have been a huge risk for the overall system reliability. Among other things, our client could invest money in a storage solution of their own. Chances are very high that it would be that or another RDBMS. You surely know that switching RDBMS is far easier than changing NoSQL db to relational, don’t you? But I could have dropped all these clever excuses in favor of one and simple reason - neither of us had a production experience with MongoDB. The end.



So, as you might have guessed by now, we chose to store our data in RDBMS - MySQL. As for the data volume - we decided to postpone this problem till we face it. After all, we could have not succeeded with this project at all.
Ok, so we now use relational storage, but what happened  to data? Is it relational now? We introduced canonical events model and started to translate any message arrived within any protocol supported into this model. Canonical events model effectively defines the data schema, we know the meaning of information we store and get rid of what we don’t care about. We threw every event message in one big table in denormalized form. We filled cashiers, cashdesks and goods dictionaries in parallel, duplicating data. Event itself can already include every goods or cashier attribute. Events table had no foreign keys to dictionaries, after all the fact that we used relational database didn’t bind us to normalize ad nauseam.


After we ran our product in trial for several months, we noticed that our queries had gotten slower. Oh gosh, we forgot that our dataset increases indefinitely.
After we analysed real processes, we found out that events older than three months are, basically, rubbish. Nobody ever goes that far to the past. We had to get rid of this garbage, but we had to guarantee constant system performance. I mentioned earlier, that we have a certain load profile with almost absolute zero during the night. We might have used that timespan to run cleanup tasks deleting hundreds of thousands of records. Sounds like a good idea, doesn’t it? There is one problem with this approach. Deletion is the most expensive operation in relational database. You certainly can run the query with hundreds of thousands of rows to be deleted, but it doubtfully would complete in a reasonable timeframe. There is an easier way to clean up a table - to truncate it. But could we truncate parts of tables? Sure we can, by partitioning the table! Where is my cap badge?
We partitioned our events table by months and set up a periodic task to truncate a four months old partition every once in a while. We killed two birds with one stone with this trick. We got almost constant query performance, as queries always operated on 1-3 partitions with the majority of them reading data from the latest one, and we solved the data amount problem.


Reports.
Short note about reports.
After we got rid of NoSQL we lost a possibility to run Map-reduce tasks on our data easily. However, we were still able to aggregate data for reports of course. There is a standard approach to aggregate data in relational databases areal: OLAP. There are several solutions available out there to start analysing your relational data in many dimensions, free and paid. Yet after we analysed the structure of information we had, we came to conclusion that our database had almost a classical star ROLAP scheme. By adding facts tables that referenced dictionaries, that were dimensions by coincidence, we easily solved that not so easy task. We forked activewarehouse gem and adjusted it for our needs. We started with baked in ETL dsl to extract and transform events to facts and dimensions initially. We ran these scripts on periodic basis, yet it proved to be very error prone. Again, considering the fact that we already had ROLAP scheme at place, it suddenly dawned on me, that we could append facts at the very same moment we inserted events in our database. We introduced another subscriber responsible for just that task, and that was it. As a side benefit we got immediately actual reports. Easy-peasy.


Comet.
I won’t elaborate on why did we change async web server and async application framework in favor of Rails and Mongrel. I’ll just mention, that Rails is a really convenient and powerful tool for rapid application development, provided the application is as simple as ours.


Comet is a browser to server communication pattern incorporating a permanent connection used to push data changes from server to browser to achieve near real-time behaviour in Web.
I once led a project where we had to communicate with millions of connected devices and propagate changes to web layer in near real-time fashion. We had found a powerful and scalable tool to solve this problem. The system was built with Erlang XMPP server software MongooseIM. This marvelous piece of software allowed us to handle extensive XMPP traffic along with BOSH traffic. It also holds a transient state for web layer. Prodigy.
Should I mention, that when I hear words ‘comet’, ‘device’, ‘real-time’ together in one sentence I first think about Mongoose? This case was no exception. However, considering the fact that no hypermarket possessed a thousandth part of devices this server was capable to serve (either alone or in cluster), I decided to hold this secret weapon and opt for equally cool WebSocket and node.js technology.
WebSocket is a binary protocol, part of html5. Its main advantage is that it is a standard, rather than a proprietary technology built as a browser plugin, it is supported by most of modern web browsers. node.js is a very fast webserver with evented processing model built on top of libev. Unfortunately, we immediately found out that a Websocket is supported by IE starting with version 10, whereas our clients had IE9 installed in the majority of cases. All in all, Websocket was abandoned pretty fast, even before the first prototype was written.
With devices’ communication protocol at hand I managed to calculate the load that a huge hypermarket (huge hypermarkets have up to 50 cashdesks here in Moscow) would have generated. I got an exaggerated number of 30 events per second. It means that our comet server would have to distribute 30*Nusers events per second.
If you remember the facts I mentioned in the very beginning, it was said that there was only one case when monitoring has been involved - security supervision. Even the biggest markets hardly have more than one security desk. That means Nusers for monitoring equals 1. Moreover, if you consider the fact that you hardly ever see all the cashdesks working at the same time (the latests Ruble crisis is not a good example here), and the fact that one can hardly perceive more than 7 objects simultaneously, you’ll get a workload similar to something like this: 30*⅔*⅙ - this would be the real load on our comet server to get monitoring events.
Keeping in mind that the webserver was the least loaded part of the system, it would easily serve these requests, even with the primitive polling.



After a short prototyping iteration we ended up with solution incorporating EventMachine thread inside the web application. EventMachine hosted an amqp subscriber, consuming certain cashdesks’ events from transient queues, pushing them to memory buffers, that got popped out by a regular Rails controller serving Ajax requests issued by a client. There is one small thing to note here, we optimised the traffic a bit by doing long polling. This is how we got rid of WebSockets and node.js.


.Net.
This is controversial - throughout the story I was getting rid of system components as much as possible, and here I tell you that we brought a whole enterprise framework with us. What for?
Well, first of all, we didn’t carry it with the solution. Every modern Windows Server OS has .Net framework preinstalled as a part of a system. We used .Net to develop our Watchdog service. The fact that we deployed on-premise means, that we had absolutely no chance to remotely administer our solutions. The system had to be rock solid, which was easier to achieve with native tools. This is why I wrote it with C#. All in all, Watchdog was not required to be portable across various platforms as other services were. It was implementation that was target OS dependent, and if we used a custom written code to automatically maintain our processes on Windows it didn’t mean we were supposed to take the same approach with other platforms. Something like crontab would solve the problem on *nix systems.





ActiveX.
We were “lucky” to have ActiveX on board as all our clients used Windows workstations, and all video systems we had had integrated so far, provided ActiveX API to access video programmatically. So, we didn’t write a universal facade to video systems providing streaming and event publishing features. Instead, we sacrificed cross-platform features for the speed of development. This decision, together with many other small decisions we made, allowed us to get things done on time and on budget. After all, there were no guarantees that we would succeed, so we wanted to move fast with short sprints.


Hardware
I bet you won’t guess the hardware we needed to host the solution.
It were one or two average size servers, depending on the availability requirements our clients had. Yet there is not much you can do about availability, provided that you can’t ignore regular power outages on sites.


Conclusions
We managed to deploy our first release in production after 6 month of development with 2 developers and one designer working part time. We supplied 90% of features required to compete on market.


The reason we managed to get things done is that we focused on business features rather on technology avoiding valueless experiments.





The end.

четверг, 24 июля 2014 г.

Teach your code to speak genteelly, nil is country speach

Imagine you ask a person something, but he just stares at you in response. Or even worse, you tell one to do what you want, and he seems to get your command, but turns his back to you and you can't tell if he did what you asked him to or not. Did you imagine it?
Congratulations! You have just got nil/null, whatever represents nothingness in your programming language, in response to a function call.

The same way you think a person who doesn't react appropriately to your questions or requests is an ass-head, the same way function returning nil is a moron-function.

I do know that there are cases when you have nothing to return, but this is either a Special Case or an exceptional situation. Let's consider two examples for query and command. Yes, to be genteel your code have to comply with CQS principle. 

First - query. To be honest whether a special case or an exception is better depends very much on a context. Let's consider two query categories: query for data (information, data structure) and query for behaviour. First case is easy, you just fill your data structure with whatever it turns it to an empty state if it has a meaning or you raise an exception if you can't fulfill a query. Easy peasy. The second case is a little bit more complicated. Lets say, you write a polymorphic query function which is supposed to return objects with state and behaviour that may be used to do something. As long as it is polymorphic it is to change a behaviour when you work with descendant classes i.e. return different objects with different behaviour, but not every descendant alters a default behaviour. Oh, and there is no default behaviour. Natural choice? - Nil. But if I put it another way: to do nothing IS a default behaviour, it becomes obvious that your best bet is a Special Case with no op strategy, rather then nil, completely nothing. 

Now a command. I'd separate two kinds of commands: those that you do not expect to return a result and those you do. The first type is naturally implemented by procedures. Some programming languages have a void return type built in. This is probably the only valid case to return nothing. However, if your language of choice has no built-in tools to mark the function as a procedure then treat all commands as those that return result. In this case you'd probably return nothing in case you've failed to accomplish request, which is the worst way to do. Rather use an exception to inform a caller that his request puts you into an exceptional case when you can't process a command request.

All in all it is very easy to be polite to those who use your code and to those who read it. 

To be little bit more convincing I'll again reference one of the best programming books ever: Clean Code, and a very good discussion.

And as if that was not enough already I'd quote Sir Charles Antony Richard Hoare:
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. [...] More recent programming languages like Spec# have introduced declarations for non-null references. This is the solution, which I rejected in 1965.

P.S. Should I mention that passing nil/null as a function argument is even worse for very obvious reasons? 
P.P.S. I'd like to try a programming language without nils. Without nothing. You got it.

Cheers.

четверг, 17 июля 2008 г.

AddIns in Plugins or much of muchness. Part 1. Overview.

Совершенно верно, вы не ошиблись, и это не двоит у вас в глазах. Именно маслом маслянным я реализовал подключение стронних плагинов в композитном клиенте, построенном на SCSF (Smart Client Software Factory).

Существует много терминов для обозначения одного и того же паттерна проектирования - подключаемого модуля (plugin, addin, addon и т.п.). Ребята из P'n'P назвали подключаемые модули в CAB -plugins, а парни из Managed AddIn Framework (MAF) - addins, соответственно. Сразу возникает вопрос - а зачем вообще мешать все в кучу, если в CAB и так уже plugin является краеугольным камнем? Ответ прост - в MAF заложено множество возможностей по работе с плагинами (так и буду дальше их называть), которых нет в CAB. Например: возможность изоляции плагинов в отдельных доменах или даже процессах, возможность запуска плагинов с различными уровнями безопасности и др.

Хороший пример работы с MAF написал Sacha Barber здесь. А вообще, очень много полезной информации можно почерпнуть в блоге комманды MAF.

Дизайн системы следующий: в Shell - оболочку программы, загружаются модули CAB, один из которых управляет подключением MAF плагинов. Схематично это можно представить себе
примерно так:


Модуль App.Addins публикует в настроечную инфраструктуру UI для управления состоянием MAF плагинов. Этот модуль управляет временем жизни и окружением MAF плагинов, позволяя вам подключать или отключать те или иные плагины, выбирать с каким уровнем изоляции и в какой домен/процесс загружать выбранный плагин.

Жизнь становится интереснее с плагинами, имеющими свой собственный UI, а также публикующими настройки! Для таких плагинов модуль App.Addin позволяет выбрать в какую часть UI приложения-хоста загрузить gadget (так я называю UI MAF плагина).

Возможным расположением gadget'ов заведует модуль App.Layout, загружающий layout из loose-xaml, который можно редактировать в любом xaml-дизайнере.


Вот, в общих чертах, реализация масла маслянного. В следующих частях постараюсь рассмотреть конкретные проблемы, которые могут быть вам интересны, и их решения.

Part 2. MAF addin UI - gadget. Object model.

Part 3. WPF-interop and "airspace" notion - hosting gadgets in complex shape windows.
Part 4. Publishing MAF addin settings.
Part 5. Handling unhandled exceptions in isolated addin domains, gadget host.
Part 6. Cross-AppDomain events propagation powered by Juval Lowy's WCF Pub-Sub framework.

Удачи!

вторник, 10 июня 2008 г.

Enterprise logging system. Part 4. Facade.

Ура, я проснулся от летаргического сна.

Предлагаю сразу взяться, например за дело.

Итак вперед.

В первой части я кратко описал требования к системе и архитектуру. В этой части я расскажу как эти требования были реализованы.

Самый очевидный способ интегрировать систему в свое приложение (о неочевидных я поговорю в следующий раз) это императивное использование ее в коде. Для данного сценария в системе предусмотрен простой фасад, которой за своей простотой скрывает тот объем работы, который необходимо проделать, чтобы гарантированно доставить сообщения в хранилище и удовлетворить всем предъявленным к ней (системе) требованиям.

Как вы помните в системе предусмотрено 5 уровней логирования:

  • Trace;
  • Info;
  • Warn;
  • Error;
  • Status;
, соответственно и интерфейс фасада определен количеством и именами этих уровней.

Что же скрывают за собой эти методы? Давайте рассмотрим работу LogBook по шагам.

При первом обращении к фасаду, прежде чем выполнить метод, clr будет вызван инициализатор типа. В этом статическом конструкторе LogBook пытается сконифигурировать себя. Этот процесс он делегирует статическому классу-загрузчику Bootstrapper, который сначала пытается получить настройки для текущего клиента от сервиса, а затем производит инициаллизацию хозяина (LogBook) из локального конфига (если вдруг в момент старта сервис будет недоступен, то он сможет поднять LogBook по локальным настройкам, которые сохраняются в конфигурационном файле клиента, а если не сможет, то кинет исключение).

Далее, в зависимости от уровня логирования LogBook создаст соответствующее сообщение и отправит его на валидацию в список подключенных фильтров (важно сделать это до окончательного заполнения сообщения, чтобы не тратить память и время зря, если оно не проходит по условиям фильтров). После этого, дабы не блокировать клиента, фасад LogBook делегирует отправку сообщения RequestManager'у, который в асинхронном режиме постарается отправить его куда следует. Куда следует - это сюда: первым делом он сохранит его в локальной стратегии, а затем попытается отправить его сервису. В случае неудачной попытки, он увеличит счетчик неудачных попыток, проверит условия устаревания сообщения (по времени хранения и количеству неудачных попыток) и сохранит обратно сообщение в локальную очередь, если оно все еще валидно. За подключением сервиса следит ConnectionMonitor, который при изменении статуса подключения генерирует событие, перехватываемое RequestManager'ом.

Все методы перегружены одним дополнительным параметром - контекстом, о котором я писал здесь. Если вызывается метод без котекста, или вместо него передается null, то в качестве контекста будет автоматически подставлен LogContext.Current.

Вот в очень сжатом виде механизм работы моего enterprise LogBook'а.

Подумываю завести проект на CodePlex. Займусь этим на досуге.

Удачи!

среда, 30 апреля 2008 г.

Enterprise Logging System. Part 3. Service.

Сервис - это ядро всей системы. Это последняя инстанция, где кончают свой путь сообщения логов из всех приложений. =)


У сервиса 2 обязанности:
  • централизовать хранение настроек;
  • помещать сообщения в сконфигурированное хранилище.
соответственно выглядит его контракт:


Сообщения несут разные наборы информации, и, чтобы не перегружать базовый класс сообщения неиспользуемыми свойствами, я ввел в data contract иерархию.

Для того, чтобы сервис мог десериализовать сообщения классов-наследников базовый класс необходимо пометить атрибутами [KnownType(typeof(...))], где ... - имена классов наследников.

Metadata.

Первая задача предельно проста. Необходимо сохранять и извлекать настройки клиентов из БД. Если настроек для конкретного клиента нет, то возвращаются дефолтные настройки, общие для всех клиентов.
Метаданные хранятся в БД и извлекаются при помощи Linq2Sql классов.

Вот классы, являющиеся метаданными системы:


Клиент идентифицируется по запросу, из полей которого составляется уникальный хеш. По нему то и ведется поиск в базе.






Logs
Сохранение сообщений делегируется адаптеру системы логирования, который подключается к сервису в файле конфигурации хоста сервиса. Я писал об этом здесь: http://bobbbloggg.blogspot.com/2008/04/loosely-coupled-wcf-service.html. В системе поумолчанию используется адаптер Enterprise Library Logging AB. Так как логи должны сохраняться в надежном хранилище с возможностью репликации со всеми данными системы на основной сервер, то конечно же в роли этого хранилища выступает БД. Дабы не тащить за собой Data Access AB из Enterprise Library, т.к. мне совсем не нравится его реализация, я написал свой легковесный custom DBTraceListener, сохраняющий логи в БД при помощи класов Linq2Sql. Этот TraceListener подключается к Logging AB в файле конфигурации и легко может быть заменен на любой другой.

Вот структура таблиц логов в БД.


Linq2Sql поддерживает паттерн отображения наследования с одной таблицой (см. Фаулер). Поэтому в таблицу введено специальное поле дискриминатор (DiscKey) и linq2sql классы образуют повторяющую LogMessage иерархию.


Все сообщения разделены на категории по уровню (LogLevel). Также в системе по умолчанию настроена одна дополнительная "специальная" категория сообщений - CriticalErrors, к которой подключен EmailTraceListener, который при деплойменте может быть настроен на почтовый ящик администратора системы и в случае получения сообщений о критических ошибках он будет получать письмо по почте, с тем чтобы привлечь его внимание к проблеме. Распределением сообщений по категориям занимается LoggingSystemAdapter.


В следующий раз я опишу фасад системы.
Удачи.

понедельник, 21 апреля 2008 г.

Enterprise Logging system. Part1 Architecture.

Имею желание поделиться с вами своим взглядом на дизайн системы логирования масштаба предприятия.

"О чем этот сумасшедший?"

Я о той части SOA инфраструктуры, которая отвечает за логирование событий в enterprise. С началом разработки данной системы мы начинаем лавировать в сторону настоящей SOA инфраструктуры, малыми галсами приближаясь к нашему видению такой модной нынче SOA концепции как ESB.

"Точно двинутый, ничего не понятно же".

Да блин, все просто - нужна система, в которую централизовано будут сваливать свои логи все приложения, работающие в сети одной компании.

Итак задачи, ограничения и требования к данной системе:


  • должна быть предельно проста в применении разработчиками;
  • должна функционировать в распределенной системе - конечная точка сервис;
  • возможность работать в отключенном режиме;
  • нельзя расчитывать на то, что на клиенте будет установлен SQL сервер, поэтому должна быть возможность подключать различные стратегии кеширования;
  • возможность подключения различных библиотек логирования;
  • возможность настройки уровня, категорий и пр. данных сообщений, которые будут отправляться с клиента;
  • сохранение логов в БД сервера, с тем чтобы реплицировать их с остальными данными на основной сервер;
  • возможность настройки альтернативного хранилища/цели сообщений логов (e-mail, event log…) (при помощи сторонних библиотек логирования);
  • возможность передать некий контекст;
  • управление исходящим траффиком
  • ...

Теперь обратимся к литературе. На codeproject я нашел несколько статей, в которых описано решение подобных задач:

В статьях предлагается уже готовое решение - библиотека для логирования с возможностью ведения логов на сервере. В принципе это решение могло бы устроить, но в нем не реализовано одно из основных требований - возможность работы в отключенном режиме. К слову сказать, в данной библиотеке можно реализовать подобное поведение, но это для этого потребовалось бы дописывать некоторое количество кода. Да к тому же, держа в голове тот факт, что в конце концов данная библиотека будет интегрирована в нашу SOA инфраструктуру, лучше иметь полный контроль над кодом. Поэтому я принял решение не использовать данную библиотеку, но несколько светлых идей оттуда почерпнул. В частности модель провайдеров для подключения локальной стратегии и модель фильтров для управления исходящим трафиком.

В качестве стронней библиотеки логирования я рассматривал следующие проекты:

Функциональность и модель программирования во всех этих проектах примерно одинаковая и включает в себя возможность логировать в такие хранилища как Event log, база данных, E-mail, и т.д. и т.п. В конечном итоге выбор я остановился на Enterprise library, во-первых потому что я уже имею опыт работы с Enterprise Library и использовал Validation Application Block в слое бизнес объектов в проектах (писал об этом здесь), а во-вторых чтобы сохранить единую базу, на которой строятся все проекты.

После обработки материалов и требований у меня сложилась следующая архитектура.


Характерными особенностями ее являются:
  • логирование всегда ведется на сервере, поэтому сервис логирования является основной компонентой системы;
  • сервис слабо связан с непосредственным кодом логирования и библиотекой, берущей на себя эти функции при помощи уже описанного мною приема http://bobbbloggg.blogspot.com/2008/04/loosely-coupled-wcf-service.html; Это оставляет за мной возможность безболезненно сменить библиотеку логирования, в случае, если звезды изменят свое расположение и я передумаю использовать Logging AB;
  • клиент работает с библотекой через фасад, а всю работу по конфигурированию, кешированию и работе с сервисом система берет на себя прозрачно для клиента, при этом по возможности не блокируя его;
  • сервис логирования - WCF singleton-сервис; Его функция централизовывать хранение настроек и делегировать сохранение сообщений адаптеру библиотеки логирования;
  • сохранение логов в базу данных производится через подключаемый к Logging AB Custom TraceListener.

Сообщения в системе имеют несколько уровней, которые в порядке приоритета распологаются в следующим образом:

  • Trace;
  • Info;
  • Warn;
  • Error;
  • Status;

Настройки клиентов, уникально себя идентифицирующих, хранятся в БД. При первом обращении к фасаду системы, клиент обращается к сервису с просьбой передать настройки, если сервис недоступен - пытается "поднять" их из локального конфига.

Локальные стратегии кеширования подключаются к клиентской части системы при помощи модели провайдеров ASP.NET. Так же система расширяется за счет подключаемых фильтров, которые контролируют исходящий траффик. По умолчанию в систему включены провайдер локальных стратегий, стратегия кеширования в оперативной памяти (для тестирования), стратегия для кеширования в IsolatedStorage, фильтр траффика по уровню сообщения.

В серверной части система расширяется при помощи модели расширений WCF, подключая к сервису адаптер, которому делегируется работа с библиотекой логирования. В систему встроен адаптер для работы с EnterpriseLibrary.

Всю работу по отправке сообщений в сервис берет на себя компонента системы Disconnected Service Agent (DSA). В Smart Client Software Library есть Application Block, реализующий данную функциональность, но по упомянутым выше причинам я предпочел не использовать готовую функциональность, а написал свой небольшой блок, опять же почерпнув из MS-ской реализации светлые идеи. Основными частями данного блока являются:

  • Connection Monitor - агент, который следит за состоянием подключения к сервису;
  • Request Manager - диспетчер запросов. Ставит запросы в очередь (локальную стратегию кеширования), пытается асинхронно отправить их, дабы не блокировать клиента;
  • Local Store Strategy - локальная стратегия кеширования, которая подключается в процессе конфигурирования.


На этом сегодня все в следующий раз подробнее о сервисе.

Удачи.