Dependency Injection, The Best Pattern
Summary
TLDRLa inyección de dependencias, aunque su nombre suene más sofisticado de lo que es, es un concepto fundamental en la programación que permite a una pieza de código utilizar otra pieza de código pasándola como parámetro en lugar de utilizarla directamente. Este enfoque desbloquea efectos secundarios poderosos, como se demuestra en la construcción de un servicio de adjuntos para una aplicación de chat de negocios. El servicio maneja múltiples ubicaciones de almacenamiento, como S3 de AWS y servidores SFTP, y permite la integración de diferentes métodos de almacenamiento como WebDav. A través de la inyección de dependencias, se pueden crear interfaces y clases que se adapten a las necesidades específicas de cada cliente, facilitando la gestión de configuraciones y la capacidad de pruebas unitarias. La inyección de dependencias permite una arquitectura modular y fácil de modificar, lo que se ve particularmente útil para adaptarse a los cambios en los requisitos del negocio o en los acuerdos con proveedores de servicios de seguridad y almacenamiento. Además, permite la creación de interfaces comunes para componentes como escáneres de virus, generadores de vistas previas y servicios de cifrado, lo que facilita la transición entre diferentes implementaciones y la realización de pruebas de integración y unitarias.
Takeaways
- 🧩 **Inyección de Dependencias Sencilla**: La inyección de dependencias es el proceso de pasar objetos de una clase a otra en lugar de crearlos internamente, lo que puede hacer que el código sea más flexible y fácil de mantener.
- 🔍 **Separación de Concerns**: Al utilizar la inyección de dependencias, se separan las responsabilidades del código, lo que permite una mejor comprensión y manejo del mismo.
- 🚀 **Flexibilidad en la Configuración**: Permite configurar el comportamiento del sistema sin modificar el código base, simplemente cambiando las dependencias que se inyectan.
- 🛠️ **Facilidad para Cambios**: Es fácil adaptar el servicio para diferentes necesidades al inyectar diferentes implementaciones de una interfaz.
- 📂 **Manejo de Almacenamiento Diverso**: La inyección de dependencias permite manejar múltiples sistemas de almacenamiento, como S3, SFTP y WebDav, según las preferencias de los clientes.
- 🔗 **Interfaces y Fábricas**: Se utilizan interfaces y fábricas para crear y proporcionar las implementaciones correctas de los servicios, lo que simplifica el código y lo hace más mantenible.
- 🛡️ **Prevención de Errores**: Al requerir que se pasen exactamente los valores necesarios a través de la inyección de dependencias, se reduce la probabilidad de errores en la configuración.
- 🔄 **Procesos de Subida Modulares**: Los procesos de subida de archivos, como la detección de virus, el escalado de imágenes y la generación de vistas previas, se modularizan y se inyectan según sea necesario.
- 🔐 **Encriptación y Seguridad**: La inyección de dependencias también se utiliza para manejar la encriptación de archivos, inyectando servicios de claves y encriptación para garantizar la seguridad de los datos.
- 🧪 **Pruebas y Mocks**: La inyección de dependencias facilita la prueba de código al permitir la inyección de implementaciones ficticias o simuladas, lo que aísla componentes para pruebas unitarias.
- 🔄 **Cambio de Implementaciones**: La capacidad de inyectar diferentes implementaciones de una interfaz hace que sea fácil cambiar o actualizar componentes del sistema sin afectar el resto del código.
Q & A
¿Qué es la inyección de dependencias y cómo se describe en el script?
-La inyección de dependencias es un principio de programación que permite a una pieza de código utilizar otra pieza de código sin depender directamente de ella, sino que se le pasa por referencia. En el script, se describe como un proceso simple que permite efectos secundarios poderosos, permitiendo la modularidad y la facilidad de prueba de código.
¿Cómo se utiliza la inyección de dependencias en la aplicación de negocios mencionada en el script?
-En la aplicación de negocios, la inyección de dependencias se utiliza para manejar el almacenamiento de archivos adjuntos. Se crean interfaces y múltiples implementaciones para diferentes servicios de almacenamiento, como S3, SFTP y WebDav. Esto permite que el código que realiza la solicitud de almacenamiento no sepa ni se preocupe por el destino exacto del archivo, mejorando la claridad y la flexibilidad del código.
¿Por qué es problemático tener un único método de carga con múltiples condiciones 'if' para manejar diferentes destinos de almacenamiento?
-Tener un único método de carga con múltiples condiciones 'if' hace que la clase tenga muchas responsabilidades, lo que hace que el código sea feo y difícil de entender. Además, el código se mezcla para manejar diferentes protocolos y hay muchas rutas que puede tomar, lo que lo hace más complejo. También aumenta la posibilidad de errores al requerir una gran cantidad de información específica del destino que debe ser proporcionada por el llamador del método.
¿Cómo se simplifica el proceso de carga de archivos adjuntos utilizando la inyección de dependencias?
-Al utilizar la inyección de dependencias, se crean interfaces y se inyectan las implementaciones correspondientes en el controlador de la solicitud. Esto significa que el controlador no necesita conocer los detalles de la implementación de almacenamiento, simplificando así el proceso y centrando la lógica de negocio en la solicitud en sí.
¿Cuál es el propósito de los escaneres de virus y cómo se implementan en el servicio de carga de archivos?
-Los escaneres de virus se utilizan para verificar las firmas de virus en los archivos cargados. En el servicio, se implementa un escaner de virus a través de una interfaz compartida, lo que permite inyectar diferentes escaneres de virus sin alterar el código del controlador de solicitudes. Esto facilita la prueba y el cambio de escaner si es necesario.
¿Cómo se aborda el problema de la escalado de imágenes en el servicio de carga de archivos?
-Para el escalado de imágenes, se utiliza la biblioteca Sharp y se encapsula en una interfaz de escalado de imágenes. Esta interfaz también incluye un método para determinar si un archivo adjunto admite el escalado. Solo se realiza el escalado si el archivo lo admite, lo que se inyecta en la solicitud de carga.
¿Cómo se maneja la generación de vistas previas para diferentes tipos de archivos adjuntos?
-Se tiene una interfaz que representa diferentes generadores de vistas previas, cada uno de los cuales maneja un tipo específico de archivo adjunto. Se inyecta una fábrica que decide qué generador de vistas previas crear basado en el tipo MIME del archivo cargado. Esto permite que el código de carga no se preocupe por los detalles de la generación de vistas previas.
¿Por qué se utiliza el cifrado de archivos antes de almacenarlos en Amazon S3?
-El cifrado de archivos antes de su almacenamiento en Amazon S3 se realiza para proteger los datos en caso de una violación de seguridad. De esta manera, incluso si se produce un incidente de seguridad, los archivos almacenados están cifrados y, por lo tanto, más seguros.
¿Cómo se implementa el cifrado de archivos en el servicio de carga de archivos?
-Se utiliza un servicio de claves que proporciona una clave por usuario para el cifrado AES. Este servicio se inyecta en la implementación de cifrado AES, que a su vez se inyecta en la fábrica de almacenamiento. Cuando se recibe una solicitud para una empresa configurada para usar AWS, la fábrica de almacenamiento inyecta el cifrado AES en una nueva instancia de almacenamiento AWS.
¿Cómo se abordan los cambios en la configuración del servicio de carga de archivos?
-La configuración del servicio se puede cambiar desde un solo punto, lo que facilita la modificación del servicio. Por ejemplo, una vez que se cierra el acuerdo con Synergy Security, solo se necesita cambiar dos líneas de código para cambiar el servicio de escaneo de virus. También se puede agregar soporte de vista previa para nuevos tipos de archivos de manera sencilla.
¿Por qué es beneficioso utilizar interfaces y la inyección de dependencias incluso si solo hay una implementación?
-El uso de interfaces y la inyección de dependencias permite controlar los puntos de conexión y elegir qué implementación utilizar. Esto no solo se utiliza para elegir la implementación en el servicio de producción, sino que también permite inyectar implementaciones falsas o simuladas para pruebas. Esto facilita la prueba de aislamiento de secciones de código sin necesidad de modificar la estructura del código.
¿Cómo se facilitan las pruebas con la arquitectura basada en inyección de dependencias?
-La arquitectura basada en inyección de dependencias facilita las pruebas al permitir inyectar implementaciones simuladas o falsas. Esto permite aislamiento de secciones de código durante las pruebas, lo que hace que el proceso de prueba sea más sencillo y menos invasivo. Además, si se necesita probar un método privado o establecer una variable interna, esto puede ser una señal de que se debe considerar la posibilidad de extraer y aislar partes del código mediante la inyección de dependencias.
Outlines
🔌 Introducción a la Inyección de Dependencias
La inyección de dependencias se presenta como un concepto fundamental en la programación que permite a una pieza de código utilizar otra sin necesidad de conocer detalles sobre su implementación. Se centra en el ejemplo de una aplicación de negocios donde los usuarios pueden chatear, enviar imágenes y archivos, y cómo se maneja el proceso de envío de archivos a través de un servicio de adjuntos. Este servicio es responsable de almacenar, recuperar y procesar todos los adjuntos. Se discute cómo la inyección de dependencias puede mejorar la estructura y flexibilidad del código, especialmente al manejar múltiples ubicaciones de almacenamiento según las preferencias de los clientes.
🛠 Procesos de Subida y Seguridad de los Archivos
Se describen los diferentes procesos que un archivo atraviesa una vez que es enviado por un usuario. Incluye la verificación de virus, el escalado de imágenes, la generación de vistas previas y el cifrado de archivos antes de su almacenamiento en S3 de Amazon Web Services. Además, se aborda la importancia de la capacidad de cambiar fácilmente estos procesos según las necesidades del negocio o los cambios en las soluciones de seguridad, como el cambio de escáneres de virus o la adición de soporte para nuevos tipos de archivos.
🧩 Ventajas de la Inyección de Dependencias para la Pruebas y la Flexibilidad
Se destaca cómo la inyección de dependencias permite la creación de interfaces que facilitan la prueba de componentes individuales y la integración de nuevas funcionalidades sin alterar la estructura principal del código. Se menciona la utilidad de las interfaces para inyectar implementaciones simuladas o ficticias durante las pruebas, lo que permite aislamiento y verificación de la lógica de negocio sin depender de servicios externos. Además, se invita a los suscriptores de Patreon a realizar un pequeño experimento práctico con el servicio de adjuntos proporcionado, cambiando la inyección de dependencias y compartiendo los resultados.
Mindmap
Keywords
💡Inyección de dependencias
💡Interfaz
💡Almacenamiento de adjuntos
💡S3 de AWS
💡Factory
💡Escaner de virus
💡Escalado de imágenes
💡Generación de vistas previas
💡Cifrado AES
💡Pruebas unitarias
💡Desarrollo y producción
Highlights
Dependency injection is a simple concept but unlocks powerful side effects
Example business app allows users to chat, send pictures and files
File attachments are uploaded to an attachment service for storage and processing
Default storage location is Amazon S3, a simple storage service
Some clients require multiple storage locations like SFTP or WebDav
Using if statements and optional variables makes code complex and error-prone
Dependency injection allows configuring and switching implementations easily
Create an interface for attachment storage with an upload method
Implement different storage options like S3, SFTP, WebDav by implementing the interface
Pass the storage implementation into the request handler instead of configuration
Use a factory to simplify creating storage instances from company configuration
Upload process includes virus scanning, image scaling, preview generation, encryption
Use shared interfaces to easily switch between different virus scanners
Image scaling can be injected through an ImageScaler interface
Preview generation uses an interface with different implementations for file types
Inject a factory to choose the right preview generator based on file type
Encryption uses AES with a per-user key from a KeyService
Injecting dependencies makes it easy to swap in fake implementations for testing
Interfaces allow isolating components for testing in isolation
Dependency injection promotes clean, maintainable, testable code
Transcripts
Dependency injection is a term
I don't love because it sounds a lot more fancy than it is.
Dependency injection is simply when you have a piece of code
which uses another piece of code, and instead of using that code
directly, it's passed in instead.
When you pass something in to be used, we call it injection.
We inject the dependent code into the code that uses it.
While this part is quite simple, it unlocks
some very powerful side effects that we're going to cover.
We have a business app where users can chat with their coworkers.
They can also send pictures and files to each other.
When a user sends something, the file gets uploaded to our attachment service.
The attachment service is responsible for storing, retrieving and processing
all attachments.
We're
going to build up this whole service using dependency injection
and we'll see what it enables us to do.
When a user sends a message
with an attachment, the message text gets sent to our standard chat service.
We want people to receive their messages almost real time.
So this service is all about speed.
The attachment, on the other hand, gets uploaded to our attachment service.
There's an end point in our note service
/attachment/upload that the app connects to and uploads a file.
The attachment gets stored on the disk temporarily, processed in a few ways
we'll talk about and then uploaded to its final destination.
The default storage location is an S3, a part of Amazon's Web services.
It's a simple storage service that lets you put up files and pull them down.
We have some code here that takes the uploaded file
and then uploads it to S3.
Unfortunately, simple and elegant doesn't always like to co-exist with business.
While S3 is nice and straightforward and most of our clients are okay with it,
we have a few firms that don't want us to permanently store their data.
This means we actually need our service to handle multiple storage locations.
Then, depending on which company a user is from,
we need to put their attachments in the right place.
Most of these picky clients just give us an SFTP server to connect to,
but one really wanted us to use WebDav.
Our first thought might be to simply extend our upload code with some
if statements
and then have the caller of the upload method pass in where to upload the file.
This is awkward for a few reasons.
First, this one class has a ton of responsibility, making the code pretty ugly.
The code for SFTP is intermingled with the code
from AWS and WebDav, even though they're pretty different.
There's a lot of paths the code can take, and that makes the code
harder to understand.
Second, using the class isn't very simple.
We have this one upload function
which needs a bunch of info for where we're going to upload the attachment.
But what info it needs is very different depending on where it's going.
If it's AWS, we need the AWS keys.
If it's SFTP
we need the address and private key and WebDav needs a URL and auth key.
So we're kind of forced
to have a bunch of these
optional variables that need to be filled out in certain cases,
and then comments to tell you which ones to fill out.
This makes it pretty easy for the caller to make a mistake.
And finally, the part of the code that actually calls upload over here
needs to have all of this destination specific context to perform the upload.
But really, at this phase, it just wants to upload.
The part of the code that knows best which company a user is from
and can deduce where the file should go is up here at the beginning of the request.
But right now, we're forced to pass all of this information around.
Let's see what happens if we use dependency injection instead.
Let's create an interface that represents our attachment storage,
which contains a key upload method
that does what the request handler wants to do: Upload an attachment.
Then we create three different implementations of the storage interface.
The configuration for each is passed into their constructor.
There's no
more optional variables that sometimes need to be set.
We require exactly these values and you get an error if you forget one.
So now, once the user is authenticated and we know which company they're from,
we create the storage that the request handler should use.
Instead of the configuration needing to be passed all the way to the request
handler, only the storage is passed to, or injected into, the request handler.
It's not aware of which storage is passed in
or where the file is going, it just knows that it can call upload.
That said, this construction code is still a little
too complex to put here, so let's see if we can clean this up.
If we look at the input here, it's really just this company configuration
and the output is the storage which conforms to the storage interface.
So we can just move this out to a factory.
Great.
But saving to the final storage destination
is the last step of the process.
We have all these other stages that the upload goes through.
We run each upload through a virus scanner.
This checks the files for signatures of obvious viruses.
Then if the file’s an image, we scale it down
to a max width of 2500 pixels.
This is what we display to the user when they click on an image
because it uses less bandwidth and loads faster.
Then the file goes through preview generation.
This is basically the thumbnail that pops up underneath an attachment in the chat
so the user can see what the attachment is without fully downloading it.
Then the last step is encryption.
If we're storing the files on Amazon's S3, we pre encrypt the files
before sending them up.
That way, if there's a security breach at Amazon,
we can say they're encrypted
when we have to send out one of those: ‘We were hacked, btw’, emails.
So let's see how we
can make each of these requirements fit cleanly into our service.
For the virus scanner, we currently use a scanner called Threat Protect.
However, Synergy Security Scanner has much better
detection KPIs and our plan is to switch to it.
But sadly, we haven't finalized the deal with Synergy We're only allowed to test
with it in our development environment, not in production.
No worries.
We can create a shared interface for our two scanners
and on initialization we pick one
and inject
that one into the request handler.
When we launch in development mode, synergy security is initialized
and in production the old Threat Protect scanner is created.
Our request handler just scans the files but doesn't know which
scanner is doing it.
For image scaling,
we use the sharp library in order to inject that,
we simply wrap it up in an image scaler interface.
The interface also contains a method
which tells us if an attachment supports scaling.
We injected into our upload, request and only scale
if the attachment supports it.
Preview generation is the most complex given how many types of attachments
there could be.
We have an interface that represents the different preview generators.
It takes the input file and then returns the preview image.
We have one implementation that handles document files like word docs, slides,
etc. one for videos which extracts a thumbnail from the video
and one for images.
But the image one can thankfully just reuse our image scaler.
We just inject the same image scaler from before into the image preview generator.
So we have all these preview generator classes,
but we only need one at a time depending on which file type is being uploaded.
The upload request shouldn't need to worry about these details.
We’ll inject a factory which takes on the burden of deciding
which preview generator to create.
The factory takes in the mime type of the upload and then returns
the right preview generator to use.
So now the upload can simply just ask the factory for the preview generator
and then use it.
And lastly, we have encryption.
We only have one implementation of encryption.
We use AES but the key is per user and comes from our key service.
So we inject our KeyService into the AesEncryption
and then the AesEncryption into the storage factory.
Then whenever we get a request for a company
that's configured to use AWS, the storage factory
injects the AesEncryption into the new instance of AWSstorage.
Then the upload request gets this final
constructed AWSstorage and simply calls upload without knowing
that there's this whole chain of connected functionality.
And now we have our complete architecture.
You can see that our service is configurable from this one spot,
which makes it super easy to change.
Once our deal with synergy security
goes through, it's just these two lines to change our service.
Want to add preview support for a new file type?
It just slots in like this.
No access to the key server when running the attachment service
on your local development box?
No worries.
We can just use a fixed key when running locally.
Injection basically just lets us pick and choose
from our compatible puzzle pieces and then slot them in when we need them.
You'll notice that the time
in which dependencies are injected varies a bit.
A few dependencies are resolved and injected right at startup,.
This is often the most common scenario in dependency injection.
But some dependencies that are chosen and injected when a request comes in.
In either case, the process is mostly the same.
We have some code that accomplishes something.
It lists the dependencies it needs, and so we fulfill those needs.
You might wonder why go through the hassle of creating interfaces
and injecting things when we only have one implementation?
Like we only have one implementation of encryption.
Well, there's one big thing we haven't talked about.
If you look at our architecture here, most of our components talk to each other
through these interfaces, which are injected in.
This means that each of these connection points we can control what is being used.
We've been using this to choose which implementation to use
in our production service, but we can also make them use no implementation.
We can use injection to inject fake or mock implementations instead,
which basically means we can slice and dice up our architecture
to isolate sections of code during testing.
Let's say we want to write a test for our AWS storage class.
We can use a fake S3
which we run locally that pretends to be the cloud service.
Then our test can call upload, and we can verify that a file got uploaded to S3.
But because of the encryption, we can't actually check the content
of the file and verify that it's correct and didn't get corrupted.
Not to worry.
Let's inject a mock encryption that basically just disables encryption.
When the AWS storage class asks us to encrypt a file
we'll just hand back a file that isn't actually encrypted.
Now our test is able to verify fully that uploading and only uploading works
because we've isolated it away from our dependencies.
What if we wanted to test encryption?
Well, we could mock out the key store to return a key that we control
instead of going all the way to the key service.
Or if we wanted to
integration test both our AWS code and encryption code together.
We could do that by injecting our fake key store into the real encryption
and then inject the real encryption into the AWS storage.
A key thing here is that this is easy to do.
A natural side effect of having nice code
is that it's easy to test without needing to hack around the code structure.
If you find yourself asking, how can I test a private method?
Or I need to set some internal variable in order to test.
That's a signal that you maybe need to pull some stuff out,
that you need to isolate some part of it by separating it and injecting
it instead.
I'm going to try something new with this video.
I truly think you only learn stuff by trying stuff.
So for those subscribed to my Patreon I'm going to start
including some light experiments with videos.
For this one, you can download the attachment service I wrote,
and I want you to reconfigure the service by changing the dependency injection.
And then you get to win some *Aesthetic* points
if you enter the results on the site.
Посмотреть больше похожих видео
¿Qué es y cómo funciona useEffect? Hooks de React
¿Que es IaaS?
¿Que es un WEB SERVICES?¿Para que SIRVE?:💻📝: EN 3 MINUTOS
Arquitectura de Computadoras I S7 07 Superescalar 2
Aprende lo básico sobre APIs y HTTP para CONECTAR la IA con cualquier aplicación
React Native Web - Email Router - Civeloo - Parte 13 💻 (sin editar)
5.0 / 5 (0 votes)