В последнее время ничего не пишу - сижу, ковыряюсь с WPF. Нравится очень, гибкость офигительная, но многое нужно постигать как бы сначала... Впрочем, об этом в другой раз, а пока вернусь к веб-сервисам.
Писать этот постинг меня сподвигло то, что сразу с двумя человеками из разных компаний и разных городов недавно разговаривал о том, как (и почему именно так) нужно строить архитектуру при работе с веб-сервисами.
Поскольку сейчас я как раз занят тем, что пишу одновременно и слой веб-сервисов и клиента к нему (с использованием того самого WPF), то об этом я и расскажу. Заодно прерву "затяжное молчание".
Итак, начнем есть трехслойный пирог (серверная логика, веб-сервисы, клиентская логика) с середины, то есть, со слоя веб-сервисов.
Создавая слой веб-сервисов мы создаем контракт между двумя остальными частями (серверной и клиентской). Сам же слой представляет собой набор доступных операций, этакий API, для слоя клиентской логики и является не более, чем клиентом для слоя логики серверной.
Подчеркну важный момент: в этой схеме "клиент" ничего не обязан знать о том, как там устроена серверная часть, какими данными она оперирует, в каком виде все это хранит и т.д. Клиентский слой логики "мыслит" совсем другими категориями, у него совершенно другие задачи и, быть может, даже другая бизнес-модель.
Отсюда вытекает простая рекомендация: не нужно передавать клиентскому приложению объекты бизнес-модели серверной логики. С точки зрения развития и поддержки приложения, я бы даже сказал "никогда не нужно".
Создавайте отдельные объекты для передачи посредством веб-сервисов. Такие объекты называются "контрактами данных" (Data Contracts).
Ваш слой веб-сервисов будет оперировать объектами бизнес-логики серверной части, но для передачи клиенту будет "транслировать" их и их данные в объекты-контракты данных и лишь затем передавать.
Зачем это нужно? Очень просто. Как я уже сказал, клиентское приложение, пользуясь веб-сервисом Reports, совершенно не заинтересовано "знать" бизнес-модель серверной части. И для него бизнес-сущность "User" вовсе не является набором "запись в таблицу Users + ссылка в таблицу Occupations + ссылка в таблицу Departments + 2 ссылки в таблицу Addresses по их IDшникам". Ему и надо-то воспользоваться сервисом Reports всего лишь для того, чтобы этот самый репорт отобразить, и для него User - это "монолитный" объект со всеми необходимыми адресами, должностями и т.д. А вот информация о паролях, логинах, ролях и т.д. ему совершенно не нужна, поэтому и передавать ее в сервисе Reports не за чем.
Поэтому слой веб-сервисов "берет" все эти серверные бизнес-объекты (адреса, должности), трансформирует эти данные в отдельный объект, определенный контрактом, и уже его передает клиенту.
Кроме этого, контракты данных получаются достаточно простыми и четкими, что позволяет легко использовать сервисы даже из не-.net приложений, если придется. Да и в любом случае удобнее, чем передавать сложные объекты бизнес-модели, часто имеющие свою иерархию, избыточную с т.з. веб-метода информацию и т.д.
Все счастливы.
- Счастливы в том числе и разработчики серверной логики, которые могут модифицировать объекты бизнес-логики, не боясь "сломать" интерфейс между сервером и клиентами.
- Счастливы разработчики самого слоя веб-сервисов которые могут сами решать, какие данные нужно передавать в сервисах и как, добавлять и удалять веб-службы и веб-методы, совершенно не беспокоясь о том, что в один прекрасный момент объекты серверной части поменяются (или объектов нужного типа просто нет) и они не смогут обеспечить необходимый формат данных.
- Счастливы разработчики клиентской части софта, так как они могут быть уверены, что для уже работающего функционала веб-методы вдруг не станут предоставлять "немного другие" другие данные.
Счастливы, в конце концов, те, кому все это потом поддерживать. Ибо в данном случае слои получаются слабо связанными, легко понимаемыми и легко модифицируемыми (в том числе и независимо друг от друга).
Кстати, еще о клиентской части.
Здесь я тоже предложил бы не использовать в приложении объекты (контракты данных), полученные от веб-сервиса.
Вместо этого часто лучше трансформировать данные из этих контрактов в бизнес-объекты клиентской модели.
Плюса здесь два, и оба существенные.
Первый: Вы получаете для работы "нормальные" бизнес-объекты, наделенные необходимым поведением, необходимыми свойствами, нужными для работы клиентской части приложения и т.д. Это гораздо лучше, легче, да и правильнее с точки зрения ООП, работать с объектами бизнес-модели, нежели с "безликими" наборами данных, которые вы будете пихать по очереди в немереную кучу классов-обработчиков, которые будут как-то манипулировать этими данными. В небольших (по функциональности и времени поддержки) приложениях это еще куда ни шло, но в случае серьезных проектов вы рискуете если не потерять контроль над сложностью системы, то наплодить кучу лишних обходных путей (представьте себе судьбу того, кому в этих "заплатках" потом разбираться), вместо того, чтобы продолжать работать в рамках объектно-ориентированной парадигмы.
Второй: Вы сами контролируете объекты, с которыми вы работаете.
После получения данных от веб-сервиса и трансформации их в собственные объекты клиентской бизнес-модели, программисты оной могут творить с/из этих объектов свободно что угодно.
Нужно сделать Undo? Пожалуйста, добавили необходимые методы. Нужно, чтобы объект оповещал об изменении его свойств? Пожалуйста, реализовали INotifyPropertyChanged и получили беспроблемный двусторонний байндинг в UI. Нужна собственная иерархия? Опять пожалуйста, хозяин-барин.
Итак, коротко обобщим основные рекомендации:
- Создавайте на стороне серверной логики полноценные объекты бизнес-модели и работайте с ними (а не с именованными наборами данных, за поведение которых отвечают сторонние классы, которые хрен найдешь, если не знаешь).
- Создавайте контракты сервисов (API, наборы операций, веб-методы), которые передают и принимают контракты данных (простые типы-носители необходимых для операции данных). Никогда не используйте бизнес-объекты серверной логики в качестве контрактов данных.
- Не работайте напрямую с объектами-контрактами данных в клиентском приложении. Создайте полноценные объекты бизнес-модели клиентской части, и заполняйте их данными, полученными от веб-сервисов. Так вы одновременно получите возможность развивать и управлять этой моделью и избавитесь от необходимости реализовывать странные сторонние классы, отвечающие за манипуляцию данными (те самые, которые потом хрен найдешь и проконтроллируешь).
Словом, воспринимайте веб-сервисы, как средство передачи данных, а не объектов бизнес-модели. И будет вам простота разработки, и вы будете избавлены от необходимости поддерживать совершенно немодифицируемый (в силу очень сильной связанности всего и вся) код с разбросанным по разным местам поведением.
Пример (упрощенный) веб-метода, возвращающего данные о производителе товара:
| public DataTypes.Brand GetByID(int id) |
| { |
| BusinessLogic.ManufacturerGetById action = new BusinessLogic.ManufacturerGetById(); |
| BusinessEntities.Manufacturer m = action.Execute(id); |
| |
| return Translators.BrandToManufacturerTranslator.Translate(m); |
| } |
Здесь ManufacturerGetById - это класс-action, умеющий вернуть сложный объект бизнес-модели серверной части по его идентификатору. Далее он трансформируется в простой объект контракта данных Brand, который и будет предоставлен клиенту.
Клиент же, получив эти данные, таким же способом заполняет ими свой объект
Brand : Company, INotifyPropertyChanged, ISearcheable
с которым и продолжает работать дальше.