SOLID in Golang: The Dependency Inversion Principle

This is the dependency inversion definition written by Bob. We will use it to understand better. The first is the abstraction property of OOP. We will use concepts and other techniques to discover the behavior we need to implement them. We need to know the differences between high-level and lower-level modules.

In the Go context, high-level modules are software components used on top of an application. It is a layer that adds real business value to the application.

This could be, for example, a struct that contains logic to retrieve data from a database or send an SQS request, retrieve a value from Redis, or submit an HTTP request to an external API. What happens if the high-level component relies on one low-level? Let’s take the following example.

The high-level EmailService is defined in the code above. This structure is part of the application layer and is responsible for emails to newly registered clients. The idea is to have a method, SendRegistrationEmail, where the UserID is necessary. It retrieves the User from UserRepositoryin background and sends it to the email sender service to send the email.

For the moment, EmailSender will be ignored. Let’s concentrate on UserRepository. This structure is part of the infrastructure layer. It shows how the repository connects to the database. So the high-level component, EmailService, depends on the low-level detail, UserRepository.We can only initialize the structure if we have a database connection. Going against a pattern like this has an immediate impact on unit testing in Go, as shown in the following code:

You can’t mock anything in Go. Mocking is based on interfaces. We can declare mocked implementations for interfaces. However, we cannot do the same thing with structs. Because UserRepository is a structure, copying it cannot be done. This situation means that we need to create a lower-layer simulation. We may use the SQLMock package to simulate the Gorm link. However, there are more reliable and efficient methods of testing. Acting on large numbers of SQL queries and understanding the database schema is necessary. Each time the database is modified, we must update the unit test.

We will also have more trouble with unit testing if we move to Cassandra. Do we plan to provide dispersed storage for customers in the future? If so, there will be lots of refactoring code.

Now we know that a high-level component can rely on a lower level. What about abstractions that are based on details? Look at the code below.

With the new structure of the code, we can see the UserRepositoryinterface as a dependency on the User struct, both in the domain layer. We use UserGorm instead of the Usersstruct. This does not mirror the database schema. The infrastructure layer tops this layer. It has a ToUserfunction that corresponds to the Usersstruct. We use UserGormas, a usage detail inside UserDatabaseRepository, as the actual implementation for UserRepository. Only the UserRepositoryinterface and the User object, both in the domain, are used inside the domain and application classes. We may specify as many UserRepositoryimplementations as we like in the infrastructure. UserFileRepositoryor UserCassandraRepositoryare two options. The high-level component ( MailService ) is dependent on the abstraction. It contains a field of type UserRepository. How does the low-level element depend on the conception?

Go structs implement interfaces automatically. That implies we may add a check using a blank identifier instead of adding code where UserDatabaseRepositoryexplicitly implements UserRepository. This approach makes it easier to manage dependencies. This approach makes it easier to manage dependencies. Interfaces are dependent upon structure. We can build alternative implementations and include these whenever we need to see global dependence changes. There are many libraries available on Go: Facebook Wire and hoac Dingo. Let’s look at it.

Leave a Reply