Estoy leyendo el libro Principios, prácticas y patrones de inyección de dependencia y leí sobre el concepto de abstracción permeable que está bien descrito en el libro.
En estos días estoy refactorizando una base de código C # usando inyección de dependencia para que se usen llamadas asíncronas en lugar de bloquearlas. Al hacerlo, estoy considerando algunas interfaces que representan abstracciones en mi base de código y que deben rediseñarse para que se puedan usar las llamadas asíncronas.
Como ejemplo, considere la siguiente interfaz que representa un repositorio para usuarios de aplicaciones:
public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
}
De acuerdo con la definición del libro, una abstracción con fugas es una abstracción diseñada con una implementación específica en mente, de modo que algunos detalles de la implementación "se filtran" a través de la abstracción misma.
Mi pregunta es la siguiente: ¿podemos considerar una interfaz diseñada teniendo en cuenta asíncrona, como IUserRepository, como un ejemplo de una abstracción con fugas?
Por supuesto, no todas las implementaciones posibles tienen algo que ver con la asincronía: solo las implementaciones fuera de proceso (como una implementación de SQL), pero un repositorio en memoria no requiere asincronía (en realidad, implementar una versión en memoria de la interfaz es probablemente más difícil si la interfaz expone métodos asincrónicos, por ejemplo, probablemente tenga que devolver algo como Task.CompletedTask o Task.FromResult (usuarios) en las implementaciones del método).
Qué piensas sobre eso ?
fuente
Task
. Las pautas para sufijar los métodos asíncronos con la palabra asíncrono era distinguir entre llamadas API de otro modo idénticas (C # no se puede enviar según el tipo de retorno). En nuestra empresa lo hemos dejado todo junto.Respuestas:
Por supuesto, se puede invocar la ley de las abstracciones con fugas , pero eso no es particularmente interesante porque plantea que todas las abstracciones tienen fugas. Se puede argumentar a favor y en contra de esa conjetura, pero no ayuda si no compartimos una comprensión de lo que queremos decir con abstracción y lo que queremos decir con fugas . Por lo tanto, primero intentaré delinear cómo veo cada uno de estos términos:
Abstracciones
Mi definición favorita de abstracciones se deriva de la APPP de Robert C. Martin :
Por lo tanto, las interfaces no son, en sí mismas, abstracciones . Solo son abstracciones si sacan a la superficie lo que importa y ocultan el resto.
Agujereado
El libro Principios, patrones y prácticas de la inyección de dependencia define el término abstracción permeable en el contexto de la inyección de dependencia (DI). El polimorfismo y los principios SÓLIDOS juegan un papel importante en este contexto.
Del Principio de Inversión de Dependencia (DIP) se desprende, citando nuevamente APPP, que:
Lo que esto significa es que los clientes (código de llamada) definen las abstracciones que requieren, y luego van e implementan esa abstracción.
Una abstracción con fugas , en mi opinión, es una abstracción que viola el DIP al incluir de alguna manera alguna funcionalidad que el cliente no necesita .
Dependencias sincrónicas
Un cliente que implementa una parte de la lógica empresarial generalmente usará DI para desacoplarse de ciertos detalles de implementación, como, por lo general, las bases de datos.
Considere un objeto de dominio que maneja una solicitud de reserva de restaurante:
Aquí, la
IReservationsRepository
dependencia está determinada exclusivamente por el cliente, laMaîtreD
clase:Esta interfaz es completamente sincrónica ya que la
MaîtreD
clase no necesita que sea asíncrona.Dependencias asincrónicas
Puede cambiar fácilmente la interfaz para que sea asíncrona:
La
MaîtreD
clase, sin embargo, no necesita esos métodos sean asíncrona, por lo que ahora se viole el DIP. Considero que esto es una abstracción permeable, porque un detalle de implementación obliga al cliente a cambiar. ElTryAccept
método ahora también tiene que volverse asíncrono:No existe una lógica inherente para que la lógica de dominio sea asíncrona, pero para admitir la asincronía de la implementación, esto ahora se requiere.
Mejores opciones
En NDC Sydney 2018 di una charla sobre este tema . En él, también describo una alternativa que no tiene fugas. También daré esta charla en varias conferencias en 2019, pero ahora se renombró con el nuevo título de inyección asíncrona .
También planeo publicar una serie de publicaciones de blog para acompañar la charla. Estos artículos ya están escritos y en la lista de espera de mi artículo, esperando ser publicados, así que estad atentos.
fuente
No es una abstracción permeable en absoluto.
Ser asíncrono es un cambio fundamental en la definición de una función: significa que la tarea no finaliza cuando vuelve la llamada, pero también significa que el flujo de su programa continuará casi de inmediato, no con un retraso prolongado. Una función asincrónica y una síncrona que realizan la misma tarea son funciones esencialmente diferentes. Ser asincrónico no es un detalle de implementación. Es parte de la definición de una función.
Si la función expuso cómo se hizo asíncrona, sería permeable. A usted (no / no debería tener que) importarle cómo se implementa.
fuente
El
async
atributo de un método es una etiqueta que indica que se requiere un cuidado y manejo particular. Como tal, necesita filtrarse al mundo. Las operaciones asincrónicas son extremadamente difíciles de componer correctamente, por lo que es importante informar al usuario de la API.Si, en cambio, su biblioteca gestionó adecuadamente toda la actividad asincrónica dentro de sí misma, entonces podría permitirse el lujo de no dejar que
async
la API se filtre.Hay cuatro dimensiones de dificultad en el software: datos, control, espacio y tiempo. Las operaciones asincrónicas abarcan las cuatro dimensiones, por lo tanto, necesitan la mayor atención.
fuente
No exactamente. Una abstracción es una cosa conceptual que ignora algunos elementos de una cosa o problema concreto más complicado (para hacer la cosa / problema más simple, manejable o debido a algún otro beneficio). Como tal, es necesariamente diferente de la cosa / problema real, y por lo tanto va a tener fugas en algún subconjunto de casos (es decir, todas las abstracciones tienen fugas, la única pregunta es en qué medida, es decir, en qué casos es la abstracción útil para nosotros, cuál es su dominio de aplicabilidad).
Dicho esto, cuando se trata de abstracciones de software, a veces (¿o tal vez con la suficiente frecuencia?) Los detalles que elegimos ignorar en realidad no se pueden ignorar porque afectan algún aspecto del software que es importante para nosotros (rendimiento, mantenibilidad, ...) . Entonces, una abstracción permeable es una abstracción diseñada para ignorar ciertos detalles (bajo el supuesto de que era posible y útil hacerlo), pero luego resultó que algunos de esos detalles son significativos en la práctica (no se pueden ignorar, por lo que "filtrarse").
Por lo tanto, una interfaz que expone un detalle de una implementación no tiene fugas per se (o más bien, una interfaz, vista de forma aislada, no es en sí misma una abstracción con fugas); en cambio, la filtración depende del código que implementa la interfaz (es capaz de soportar la abstracción representada por la interfaz), y también de los supuestos hechos por el código del cliente (que equivale a una abstracción conceptual que complementa la expresada por la interfaz, pero no puede expresarse en código (por ejemplo, las características del lenguaje no son lo suficientemente expresivas, por lo que podemos describirlo en los documentos, etc.)).
fuente
Considere los siguientes ejemplos:
Este es un método que establece el nombre antes de que regrese:
Este es un método que establece el nombre. La persona que llama no puede asumir que el nombre se establece hasta que se complete la tarea devuelta (
IsCompleted
= verdadero):Este es un método que establece el nombre. La persona que llama no puede asumir que el nombre se establece hasta que se complete la tarea devuelta (
IsCompleted
= verdadero):P: ¿Cuál no pertenece a los otros dos?
R: El método asíncrono no es el único. El que está solo es el método que devuelve nulo.
Para mí, la "fuga" aquí no es la
async
palabra clave; es el hecho de que el método devuelve una Tarea. Y eso no es una fuga; Es parte del prototipo y parte de la abstracción. Un método asíncrono que devuelve una tarea hace exactamente la misma promesa hecha por un método sincrónico que devuelve una tarea.Entonces no, no creo que la introducción de
async
formas sea una abstracción permeable en sí misma. Pero es posible que tenga que cambiar el prototipo para devolver una Tarea, que "pierde" al cambiar la interfaz (la abstracción). Y dado que es parte de la abstracción, no es una fuga, por definición.fuente
Esta es una abstracción permeable si y solo si no tiene la intención de que todas las clases implementadoras creen una llamada asincrónica. Podrías crear múltiples implementaciones, por ejemplo, una para cada tipo de base de datos que admitas, y esto estaría perfectamente bien suponiendo que nunca necesites saber la implementación exacta que se utiliza en todo tu programa.
Y aunque no puede aplicar estrictamente una implementación asincrónica, el nombre implica que debería serlo. Si las circunstancias cambian, y puede ser una llamada sincrónica por cualquier razón, entonces es muy posible que deba considerar un cambio de nombre, por lo que mi consejo sería hacerlo solo si no cree que esto sea muy probable en el futuro. futuro.
fuente
Aquí hay un punto de vista opuesto.
No pasamos de regresar
Foo
a regresarTask<Foo>
porque comenzamos a querer el enTask
lugar de solo elFoo
. De acuerdo, a veces interactuamos con elTask
código pero en la mayoría de los códigos del mundo real lo ignoramos y simplemente usamos elFoo
.Además, a menudo definimos interfaces para admitir el comportamiento asíncrono incluso cuando la implementación puede o no ser asíncrona.
En efecto, una interfaz que devuelve un
Task<Foo>
le indica que la implementación es posiblemente asíncrona, lo sea realmente o no, aunque le importe o no. Si una abstracción nos dice más de lo que necesitamos saber sobre su implementación, es permeable.Si nuestra implementación no es asíncrona, la cambiamos para que sea asíncrona, y luego tenemos que cambiar la abstracción y todo lo que la usa, es una abstracción muy permeable.
Eso no es un juicio. Como otros han señalado, todas las abstracciones se filtran. Este tiene un mayor impacto porque necesita un efecto dominó de asíncrono / espera en todo nuestro código solo porque en algún lugar al final puede haber algo que sea realmente asíncrono.
¿Suena eso como una queja? Esa no es mi intención, pero creo que es una observación precisa.
Un punto relacionado es la afirmación de que "una interfaz no es una abstracción". Lo que Mark Seeman declaró sucintamente se ha abusado un poco.
La definición de "abstracción" no es "interfaz", incluso en .NET. Las abstracciones pueden tomar muchas otras formas. Una interfaz puede ser una abstracción deficiente o puede reflejar su implementación tan estrechamente que, en cierto sentido, difícilmente sea una abstracción.
Pero sí utilizamos interfaces para crear abstracciones. Entonces, descartar "las interfaces no son abstracciones" porque una pregunta menciona interfaces y las abstracciones no son esclarecedoras.
fuente
¿Es
GetAllAsync()
realmente asíncrono? Quiero decir que "async" está en el nombre, pero eso se puede eliminar. Entonces pregunto de nuevo ... ¿Es imposible implementar una función que devuelva unaTask<IEnumerable<User>>
que se resuelva sincrónicamente?No sé los detalles del
Task
tipo de .Net , pero si es imposible implementar la función de forma síncrona, entonces seguro que es una abstracción con fugas (de esta manera), pero de lo contrario no. Yo no sé que si se trataba de unIObservable
lugar de una tarea, ésta podría ser implementado de forma síncrona o asíncrona para que nada fuera de la función conoce y por lo tanto no hay un escape ese hecho en particular.fuente
Task<T>
significa asíncrono. Obtiene el objeto de tarea de inmediato, pero puede que tenga que esperar la secuencia de usuarios