Контракты на отказ WCF в реальном мире

Предположим, что у нас есть метод службы, который выполняет некоторые проверки безопасности, извлекает данные из БД и сторонней веб-службы, конструирует MyDataDTO, записывает запись аудита обратно в БД. И мы хотим хорошо структурированные, гранулированные коды ошибок, не так ли? Мы хорошие мальчики и следуем стандартным правилам обработки ошибок WCF:

[FaultContract(typeof(AccessDenied))]
[FaultContract(typeof(KeyNotFound))]
[FaultContract(typeof(WsFault))]
[FaultContract(typeof(DbFault))]
MyDataDTO GetData(string key);

Теперь мы добавляем новый метод, который обновляет данные. Метод вызывает GetData() внутренне (или большую часть), выполняет проверку, обновляет данные. Таким образом, он должен иметь все ошибки GetData() duplicated плюс добавить свои собственные ошибки:

[FaultContract(typeof(InvalidState))]
[FaultContract(typeof(DataNotValid))]
[FaultContract(typeof(AccessDenied))]
[FaultContract(typeof(KeyNotFound))]
[FaultContract(typeof(WsFault))]
[FaultContract(typeof(DbFault))]
void UpdateData(MyDataDTO data);

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

Теперь представьте, что у нас есть 10 сервисов с 10 методами, такими как выше (или даже более сложными). И определение всех этих контрактов на неисправности становится кошмаром, поскольку это довольно подверженный ошибкам процесс:

  • Невозможно определить общие ошибки (например, DbFault) для всей службы
  • Вы не можете гарантировать, что неисправность, определенная в контракте на операцию, действительно будет возвращена (проблемы с копированием)
  • Вы не можете гарантировать, что вы не пропустили какую-либо ошибку, чтобы добавить к операционному контракту

Не учитывайте здесь версию интерфейса:)

Итак, вы получили изображение, если вы поддерживаете сервисы WCF на производстве. Должны ли мы вообще отказаться от контрактов на неисправность и использовать старый добрый C-стиль (например, иметь базовый класс DTOBase с свойством ErrorCode)? Уменьшить гранулярность ошибок? Как убедиться, что документация правильная/актуальная? Меня интересуют некоторые лучшие практики.

Ответы

Ответ 1

Одна из проблем с вашим первоначальным подходом заключается в том, что вы пытаетесь реплицировать множество системных ошибок/исключений, которые могут возникнуть. Поскольку сложность системы увеличивается с каждой новой функцией, количество возможных проблем, которые вы должны учитывать, возрастает экспоненциально (или больше!)

Я бы предложил следующий подход: поскольку вы создаете "систему" ​​служб и вызовов доступа, определите только FaultContracts, относящиеся к этой системе. Клиентов следует интересовать только следующие вопросы:

  • Это проблема с данными, которые я хотел, или тем, как я спросил?
  • Если нет, это проблема со мной, или это проблема, связанная с ИТ (системная авария, сетевая ошибка, проблема с базой данных, что угодно)?

С небольшой доработкой вы можете сократить количество контрактов на неисправности, которые вы должны предоставить. Например (и это не работает):

//Base class to define general problems
[DataContract]
public class SysFault
{
  //MyDataDTO-specific general error.
  [DataMember]
  public string SysMsg {get;set;}

  //boolean for "this is a problem with me or the hosting system?"
  [DataMember]
  public bool IsSystemic {get;set;}
}

//Subclass to expose synchronization issues--if that one you want to define
[DataContract]
public class SyncFault : SysFault
{
  [DataMember]
  public string SyncMsg { get;set; }
}

//Subclass to expose validation issues
[DataContract]
public class ValFault : SysFault
{
  [DataMember]
  public string ValMsg { get;set; }
}

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

[ServiceContract]
public interface IRecordSys
{
    [OperationContract]
    [FaultContract(typeof(SysFault))]  //Raised for underlying problem
    [FaultContract(typeof(ValFault))]  //Raised if there is an issue with the key value
    MyDataDTO getData(string key);

    [OperationContract]
    [FaultContract(typeof(SysFault))]  //Raised for underlying problem elsewhere
    //Raised for some issue, such as unable to get two subsystems to update properly
    //with the given data
    [FaultContract(typeof(SyncFault))]
    void update(MyDataDTO data); 
}

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

Ответ 2

Ну, вы можете реализовать это:

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)

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

http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler.providefault.aspx

или еще лучше с образцами:

http://blogs.msdn.com/b/pedram/archive/2008/01/25/wcf-error-handling-and-some-best-practices.aspx