software programacion presentación patron negocios negocio ingenieria ejemplos datos capas capa arquitectura c# architecture domain-driven-design

c# - programacion - ¿Qué tipo de resultado debería devolverse desde la capa de servicio?



programacion en 4 capas (6)

Digamos, por ejemplo, que tengo un PostsService que crea una publicación:

public class PostsService : IPostsService { public bool Create(Post post) { if(!this.Validate(post)) { return false; } try { this.repository.Add(post); this.repository.Save(); } catch(Exception e) { return false; } } }

El problema con esto es que si se lanza una excepción durante las acciones del repositorio, se tragará. Create() devuelve false y todo lo que el consumidor sabe es que la Post no se agregó, pero no sabe por qué .

En cambio, se me ocurrió tener una clase ServiceResult :

public class ServiceResult { public bool Success { get; private set; } public Exception Exception { get; private set; } }

¿Sería este un buen diseño? ¿O es necesario que informe estos errores al consumidor? Es suficiente decir "Se produjo un error al agregar la publicación". y luego registra la excepción dentro del servicio?

Cualquier otra sugerencia es apreciada.


(Descargo de responsabilidad: En mi experiencia he trabajado mucho más con la exposición / consumo de servicios entre entornos, donde la funcionalidad incorporada en algo como WCF no siempre es útil y no siempre se tiene control sobre el cliente que consume el servicio .)

Cada vez más en mis diseños de servicio me he movido hacia un sistema de solicitud / respuesta. (En realidad, he utilizado mucho la biblioteca agatha-rrsl a este respecto). En ese diseño, tengo un objeto BaseRequest y un objeto BaseResponse , del cual heredan todos los tipos de solicitud y respuesta.

Continuando con lo que estabas pensando, esa clase ServiceResult serviría bien como el comienzo de dicha BaseResponse . No necesariamente necesita la excepción real, pero algunas cosas que tiendo a incluir son:

  1. Un bool indica el éxito o el fracaso de cualquier solicitud, incluso una que arroje resultados reales sobre el éxito.
  2. Un IList<> de objetos de Message personalizados, que a su vez contienen un tipo de mensaje de algún tipo (información, advertencia, error, etc.), así como el texto del mensaje (y tal vez un mensaje de texto de usuario adicional para las necesidades de visualización de la interfaz de usuario), seguimiento de pila si es relevante, etc.

Yendo más allá, también me gustaría incluir algunas cosas en BaseRequest , como el usuario de origen, el host, etc.

La idea es que el servicio en sí nunca arroje una excepción en bruto. Quienquiera que esté consumiendo el servicio siempre debe esperar obtener una respuesta válida, incluso si esa respuesta indica una falla de algún tipo. Debería esperar que alguna información básica vuelva cada vez y sepa cómo manejar esa información.


Creo que depende del alcance de por qué . Algunos motivos de falla no deberían afectar al consumidor, en mi opinión, y estos deberían registrarse en el servicio. Por otro lado, hay casos en que el consumidor debe ser informado de estos motivos, por lo que además de registrarlos, debe indicar la causa del error de alguna manera.

Por ejemplo, supongamos un caso de uso muy común de un servicio de registro de usuarios. Es bastante probable que tenga un atributo único que identifica al usuario (usuario, correo electrónico, etc.). Esta singularidad debe ser aplicada por el servicio (y probablemente a nivel de base de datos también). Ahora, si el consumidor está tratando de registrar un usuario y falla porque se viola la restricción, el consumidor debe ser notificado de estas razones (para que pueda probar con algún otro manejador de usuario). Las validaciones fallidas caen en el mismo escenario, la mayoría de las veces.

Si hay algún tipo de problema interno de DB, por otro lado, el consumidor no debe ser informado de los detalles (porque esa es la responsabilidad exclusiva del servicio, y no es algo de lo que el consumidor pueda hacer nada).

Teniendo eso en cuenta, diría que devolver un booleano es insuficiente en muchos casos. Entonces, tener algún tipo de tipo ServiceResult no suena como una mala idea. Sin embargo, no estoy seguro de incluir la Exception . Pero quizás pueda crear algún tipo de ServiceExpection, específico de su servicio. A veces, los códigos de error también están bien para esto.


Es una muy mala idea atrapar todas las excepciones. Solo captas lo que razonablemente puedes manejar. No puede manejar todas las excepciones, entonces, ¿por qué lo atrapa? Para evitar un rastro de pila?

Si no quieres que tus usuarios vean un rastro de pila horrible, lo entiendo, pero sí quieres que muera y muera horriblemente para que algo no se corrompa.

Digamos que el servicio arrojó una excepción OutOfMemoryException, que acaba de capturar, y no sabrá si su aplicación está filtrando la memoria como un tamiz o está asignando objetos grandes de manera incorrecta.

Eso es algo malo

Si no quiere que su usuario vea lo que estaba mal, entonces debe cambiar su captura para decir:

catch (Exception e) { //send it to yourself, log it. Just don''t swallow it. LogErrorAndEmailDevTeam(e); throw new SaveFailedException("We''re sorry. /n" + "It''s not your fault, but something bad happened./n/n" + "We''ve been notified and will fix it as soon as possible."); }

Pero aún mejor sería dejar que la aplicación muera en una muerte horrible en lugar de atrapar esa excepción para que sepa rápidamente cuando algo sale mal.

No desea que su aplicación continúe en un estado dañado, pero eso es lo que hace catch (Exception) . ¿Estás realmente seguro de que puedes manejar lo que sea que se arroje?


Sí, la clase ServiceResult es una buena idea, pero no incluiría una excepción, hay momentos en los que desea informar errores pero no quiere / necesito crear / arrojar / atrapar una excepción, sino que incluiría una colección de mensajes de error o un tipo de enumeración que indica lo que salió mal, algo así como MembershipCreateStatus .

Además, no atrape Exception , puede hacer que los errores sean difíciles de encontrar.


Tenga en cuenta que uno de los beneficios clave de una arquitectura orientada a servicios es desacoplar componentes del sistema. Cuanto más deben saber las capas de llamada sobre el servicio, más acopladas se vuelven las capas. Siguiendo esta lógica, es mejor que la capa de llamada sepa lo menos posible sobre el error, todo lo que necesita saber es que la llamada de servicio falló.

¿La capa de llamada realmente necesita saber por qué falló el servicio? ¿Qué puede hacer realmente al respecto? ¿Es solo para que pueda mostrarle un mejor mensaje al usuario? En este caso, ¿el usuario realmente se preocupa por los detalles? Es fácil pensar que el usuario quiere saber más, solo porque usted como desarrollador lo hace. Pero los desarrolladores deberían recibir información sobre los mensajes de error de los registros, no de los mensajes del usuario final.


Si está utilizando WCF, recomendaría en contra de un ServiceResult , o algo similar para hacer frente a las excepciones en una capa de SOA.

Debe definir un FaultContract para su método. Esto permitiría que el código consumidor conozca los tipos de excepciones que se pueden generar y las maneje en consecuencia utilizando los bloques try/catch tradicionales.

Esta pregunta de tiene una implementación completa de los Contratos de falla .

Todavía podría tener su interfaz simplemente decir "Ha ocurrido un error", pero usar un contrato de falla permitiría un mejor manejo de errores en la capa de IU de su código si se vuelve necesario en el futuro.