The “Real” Clean Architecture in Android: S.O.L.I.D. | by Denis Brandi | Jul, 2022

To start out, let’s say what CA just isn’t:

  1. A template to comply with
  2. An pointless boilerplate that slows you down (except you do it flawed)
  3. A brand new fashionable structure that works solely on Android ( you iOS, Internet, and Backend guys)

So what’s Clear Structure?

The quick reply is:

Clear Structure is the end result of the S.O.L.I.D. ideas.

Therefore if you happen to don’t comply with them you aren’t doing CA, you might be simply following a template that may by no means make sense.

My rant towards different articles is that they hardly ever point out S.O.L.I.D ideas and so they focus solely on the separation of issues half (SRP), and due to that, they depart the reader with extra doubts than that they had earlier than studying the article: why does that use case have a single methodology? why do I want that interface there? why do I want a special object and mappers?

The reply to all these questions is within the S.O.L.I.D. ideas so partially 1 I’ll give attention to the idea and I’ll present how every precept shapes Clear Structure, then I’ll deep dive into every part within the following articles.

What it is advisable to notice is that S.O.L.I.D. ideas should not only a delusion or a bullet level it is advisable to put in your CV.

They’re the benchmark to find out how clear your code is.

You don’t know if in case you have the suitable design? Verify in case your code adheres to S.O.L.I.D. identical to a health care provider would examine your blood take a look at, if one thing is flawed your blood take a look at will inform, the identical is for S.O.L.I.D. and your code.

Now, simply because a precept has been violated, it doesn’t imply you will have points however if in case you have points, bugs or you might be much less productive you might be almost certainly breaking a number of S.O.L.I.D. ideas.

Take a look at S.O.L.I.D. ideas as common sense disciplines that may enable you to keep out of bother, you could not have to make use of them at all times however you higher know them!

Derived lessons have to be substitutable for his or her base lessons.

Inheritance is the tightest coupling you’ll be able to have, dangerous utilization of it results in highly-coupled and lowly-cohesive code.

Most inexperienced engineers use inheritance as the first approach to obtain reusability however this typically makes upkeep virtually unattainable to realize (and simply so you already know, repeating code is a lot better than coupling code, upkeep > reusability).

As a result of this was quite common within the early days of Object-Oriented Programming, even amongst senior builders, the LSP was the primary precept used to manage inheritance.

Let’s take a look at this over-simplistic instance (which isn’t the standard sq./rectangle instance you’ll see round) the place the LSP is violated:

Earlier than you surprise, no the LSP just isn’t violated as a result of Android Builders are underpaid (though it will make a superb argument 😈).

On this situation, the developer determined to make CTO a subclass of Worker for reusing the getSalary methodology and due to that now the app may crash if the strategy getLineManager known as on a CTO object.

I do know for sure that 99% of you wouldn’t have made this error as a result of you already know {that a} CTO is a chief and never an worker however not at all times relationships are clear within the codebase.
In reality, I guess that you’ve got seen many tasks with large BaseActivity, BaseFragment , BaseViewModel…. lessons, that’s one other clear instance of LSP violation.

So it’s nice to make use of inheritance so far as I don’t break the LSP?

As a substitute of utilizing inheritance after which switching to a different method if LSP is violated, it’s best to change your mind-set and default to composition.

99% of the stuff you need to do with inheritance could be carried out with composition so it’s best to at all times favor composition over inheritance.
If by any probability composition can’t be used, even with the assistance of any of the G.o.F. design patterns, then you need to use inheritance (by ensuring you adhere to the LSP!).

What are the most typical eventualities by which inheritance is okay to make use of?

There are a few eventualities the place inheritance (IS-A relationship) is okay to make use of as a substitute of composition (HAS-A relationship), or perhaps it isn’t however you might be pressured to make use of it, for instance:

  1. When the framework you might be utilizing requires you to make use of it (extending Actions, Fragments, ViewModels…)
  2. When doing information modeling (Animal -> Mammals…)

Excluding fashions and frameworks, often, architectural parts mustn’t depend on inheritance, therefore any structure counting on inheritance is way from being good and clear.

Testing

Testing inheritance is at all times painful and requires extra effort than testing composition.

When implementing a brand new subclass you not solely have to check the brand new public strategies added and those that you’re overriding from the guardian class, however you even have to check all the opposite strategies of the guardian class that you’re not overriding!

In spite of everything, you don’t need adjustments to the guardian class to interrupt your subclass habits, don’t you?

Testing composition as a substitute is a bit of cake since your checks gained’t care about how the collaborator works, they are going to solely care about your collaborator’s contract which might simply be mocked/stubbed/faked… if required.

References:
* Clear Structure, Chapter 9 (LSP)
*
Origin of LSP

A category ought to do one, and just one, factor.

Hmmm… you meant “A perform ought to do one, and just one, factor?”.
The flawed precept, attempt once more.

A category ought to have one, and just one, cause to vary.

Higher, however perhaps rephrase it in a method that explains what that “cause to vary” is…

A category must be accountable to 1, and just one, actor (group of customers or stakeholders).

Now we’re speaking!
I’m utilizing the time period “class” as a result of we work within the Java-Kotlin realm, a extra generic description is a “module (not a java module) or a supply file that incorporates a cohesive set of features and information constructions”.

“Cohesion” is the key phrase within the SRP: the strategies of the category have to be cohesive, if they aren’t it’s best to transfer these strategies to a different class.

Who’re the actors? There are literally many, and subsequently there are going to be many lessons. “Logging in” and “Making a purchase order” will almost certainly curiosity totally different actors, the onboarding workforce and the gross sales workforce for instance. So placing them in the identical class could be a violation.

To prime that, the “Logging in” function will curiosity not solely a PO but in addition the UI/UX Designers that outlined how the person interplay is carried out and the BE workforce that supplied you the REST API (plus different actors that is likely to be concerned).

These actors, even when all concerned in the identical function will need to be impartial of one another as a lot as potential and won’t need to be affected by different actors (UI adjustments shouldn’t break your API code, API adjustments shouldn’t break your UI code, each UI adjustments and API adjustments shouldn’t break your small business logic code).

Conway’s legislation: One of the best construction for a software program system is closely influenced by the social construction of the group that makes use of it so that every software program module has one, and just one, cause to vary.

This brings within the idea of vertical and horizontal slicing: options (login, search, buy…) and layers (presentation, area, information…..).

A function set per cross-functional workforce.
A layer for every actor of that cross-functional workforce.

I’ll elaborate extra on vertical and horizontal slicing within the subsequent articles.

From SRP to CCP

On the part stage, the SRP turns into the Frequent Closure Precept (CCP) which could be phrased within the following method:

Collect into parts these lessons that change for a similar causes and on the identical instances.

Separate into totally different parts these lessons that change at totally different instances and for various causes.

The CCP is among the most essential ideas to comply with for having a rock-SOLID modularization (one other subject I’ll cowl within the following articles).

By solely making use of the SRP that is how the structure of the system would appear like:

Consequence of SRP

Once more, I’ll clarify every part within the following articles.

References:
* Clear Structure, Chapter 7 (SRP)
* Clear Structure, Chapter 13 (CCP)
* Patterns of Enterprise Utility Structure, Chapter 1

Essentially the most versatile methods are these by which supply code dependencies refer solely to abstractions, to not concretions.

That is the best precept to comply with and but it’s the one which has been extra brutally violated by builders.

By some means builders assume that it’s best to have an interface “solely within the case of a number of implementations”.
This couldn’t be extra flawed.

Any trendy Software program Structure value its identify (Hexagonal, Onion, Clear) closely depends on the DIP.
We don’t need our high-level enterprise guidelines to depend on low-level particulars.
We would like the isolation of high-level abstractions from low-level particulars.

Many builders wrestle to know the issue of isolation as a result of they aren’t conscious of how the compiler works, of transitive dependencies or that they begin writing code from the flawed class (extra on this within the subsequent article).

Let’s use the diagram from the earlier chapter (SRP) for example, the place all of the arrows level in the identical course:

View -> ViewModel -> Interactor -> Repository -> Information Retailer...

The category ViewModelmay have an import for the Interactor .
The category Interactor may have an import for the Repository …. and so forth.

What many builders don’t know is that the ViewModel additionally imports the Repository and the DataStore transitively.
Any change to the Information Retailer will set off the recompilation of the Repository which can trigger the recompilation of the Interactor and the ViewModel and so forth.. as much as the primary class of the Circulation of Management.

Structure is meant to isolate adjustments, not propagate them in all places.

On prime of that not solely does the arrows’ course dictates the compilation however it additionally dictates the primary class the developer goes to put in writing, the Information Supply on this case, which as we are going to see within the subsequent articles is the flawed class to begin implementing.

If we apply the DIP precept to the diagram of the earlier chapter, that is the way it will appear like:

The end result of SRP + DIP

Testing

Testability can also be closely affected by this precept.
Upon getting an interface, making a take a look at double turns into a mainstream job.

However I exploit Mockito, I don’t want interfaces for testing…

Sure I do know, you’ll be able to nonetheless “mock” the habits of the collaborator through the use of a mocking framework that makes use of reflection beneath the hood.
However I additionally know that Mocking frameworks make checks slower. In reality, on my machine, each time I run a take a look at class, Mockito provides an additional 5–10 seconds to the execution time, and this for a TDDer means ready 5–10 additional seconds for every TDD cycle (which continues to be sooner than not doing TDD in any respect btw).

PS: the 5–10 additional seconds turn out to be a minor downside whenever you run the checks in your CI since that is the time that Mockito takes to begin for every module (The same thing will happen with Mockk in case you might be considering that by switching framework you’d resolve the issue).

The final benefit of making your personal take a look at double as a substitute of utilizing a mocking framework is that you simply by no means must reference the collaborator’s methodology.

For instance, when mocking utilizing Mockito you might be doing one thing like this:

@Take a look at
enjoyable test1()
every time(collaborator.methodology()).thenReturn(...)
//...
@Take a look at
enjoyable test2()
every time(collaborator.methodology()).thenReturn(...)
//...
@Take a look at
enjoyable test3()
every time(collaborator.methodology()).thenReturn(...)
//...
//... you bought the purpose

Whereas, whenever you create your personal take a look at double:

class CollaboratorTestDouble: Collaborator 
enjoyable mockMethod(...) ...
override enjoyable methodology()
// stub, mock, spy...

Your checks gained’t reference the methodology anymore:

@Take a look at
enjoyable test1()
collaborator.mockMethod(...)
//...
@Take a look at
enjoyable test2()
collaborator.mockMethod(...)
//...
@Take a look at
enjoyable test3()
collaborator.mockMethod(...)
//...
//...

And by not referencing the collaborator’s strategies your checks are extra shielded from methodology signature adjustments, which finally ends up with much less code to replace and fewer code to evaluate.
Exams additionally look extra readable.

Typically you could not even must create a take a look at double as a result of you’ll be able to simply use an actual implementation.
That is often preferable when such implementation requires minimal setup (it has no collaborators) and it was created earlier than the present class beneath take a look at.

Another instances mocks simply don’t work effectively in any respect.
Take into consideration the SharedPreferences.Editor interface, each methodology returns the item occasion and the transaction just isn’t executed till apply or commit is invoked.
Many right here hand over with the mocking and simply transfer the take a look at to androidTest folder to allow them to use the true preferences however that in my view is a whole failure.
The quickest and cleanest resolution is to create a pretend like the next:

Pretend for SharedPreferences that makes use of an in-memory implementation

This pretend makes use of an in-memory implementation of the SharedPreferences interface.
By utilizing this in your take a look at class there is no such thing as a setup required and you may pre-set your preferences by simply doing
fakePreferences.putString("key", "worth").apply()
identical to you’d do with actual SharedPreferences in androidTest with the distinction that with this method your checks will run on the JVM and shall be a lot sooner (and clearly sooner than utilizing mocks).

A joke

A couple of years in the past Mockito didn’t have assist for closing lessons and when builders have been migrating their codebases from Java to Kotlin (the place lessons are closing by default) their checks have been failing.

That was a transparent signal of their dangerous structure and at that time, there have been solely a clear resolution (including an abstraction) and a unclean resolution (making lessons open).

Which resolution do you assume most builders adopted? 😈

Let’s simply say that I do know many corporations that did an enormous refactoring PR for eradicating open key phrase in all places as soon as Mockito launched their mock-maker-inline workaround (that also doesn’t work for spy and can’t be utilized in androidTest btw).

References:
* Clear Structure, Chapter 11 (DIP)
*
Hexagonal Architecture
*
Onion Architecture, Part 1
*
Martin Fowler, Mocks Aren’t Stubs

Hold interfaces small in order that customers don’t find yourself relying on issues they don’t want.

That is one primarily for us, statically-typed language customers, the truth is in dynamically-typed languages builders might even get away with simply S.O.L.D. as a substitute of S.O.L.I.D. (there are nonetheless advantages in adhering to the ISP in dynamically typed languages however I gained’t cowl them since Android is predicated on Java/Kotlin anyway).

The ISP could be seen as an extension of the DIP, given which you can’t have small interfaces if you happen to don’t have interfaces in any respect 😉.

Whereas with the DIP the aim is to guard your code from the stream of management and transitive dependencies, with the ISP we be sure our collaborators not solely are interfaces (or another kind of abstractions) however they’re additionally small so we don’t rely on one thing we don’t use, that when modified, will trigger an additional recompilation.

Let’s check out this instance:

interface UserService 
enjoyable login(...)
enjoyable logout(...)
enjoyable createAccount(...)
//...
class LoginViewModel(
personal val userService: UserService
): ViewModel()
enjoyable doSomething()
//...
userService.login(...)
//...

LoginViewModel makes use of just one methodology of UserService but it’s relying on all of them + all the item parameters they require.
Which means a change in logout will recompile LoginViewModel.
And in addition including/eradicating strategies to UserService will trigger a recompilation.

How do you resolve this? Easy, as a substitute of getting an enormous interface you create extra and smaller interfaces:

interface LoginInteractor 
enjoyable execute(...)

interface LogoutInteractor
enjoyable execute()

interface CreateAccountInteractor
enjoyable execute()

//...

ISP and SRP

If it wasn’t clear already, S.O.L.I.D. ideas should not impartial of one another, so by following one you might be almost certainly following additionally some others, and by breaking one, you might be almost certainly breaking additionally some others (that is the rationale why most builders break most of them).

Lessons can implement interfaces, so if interfaces are small then lessons are almost certainly following the SRP.

In spite of everything, which class do you assume is extra prone to break the SRP?
A category with a single methodology or a category with 100 strategies?

Typically as a substitute you need small interfaces however barely larger lessons.
Interfaces are ideally small as a result of they’re optimized for the lessons that use them.
Lessons as a substitute must be chargeable for 1 factor, this typically makes them smaller however because the SRP says, you shouldn’t cut up one accountability into a number of lessons because you are supposed to mixture cohesive strategies in a single class.

What to do on this case?
Easy, you make your class implement a number of interfaces!

From ISP to CRP

On the part stage, the ISP turns into the Frequent Reuse Precept (CRP) which could be phrased within the following method:

Don’t drive customers of a part to rely on issues they don’t want.

That is one other essential precept for modularization.
Lessons which are reused collectively must be a part of the identical part.
Just like the CCP it tells us the right way to group lessons however it additionally tells us which lessons are to not hold collectively beneath the identical part.

Identical to a category recompiles when an unused collaborator’s methodology adjustments, a part recompiles when an unused class of an imported part adjustments.

Testing

When making a take a look at double it’s fairly annoying having to take care of large interfaces since you need to stub/mock all of the strategies of the interface.

So as to keep away from this situation, you’ll be able to both use a mocking framework or adhere to the ISP.

Personally, I’m not towards mocking frameworks, I really use them in all my tasks however what I don’t like is that through the use of them builders could be tempted to interrupt 1 or extra ideas:

  • As a result of you’ll be able to mock lessons, you is likely to be tempted to keep away from interfaces (DIP 💔)
  • Since you don’t must mock all of the strategies, you is likely to be tempted to maintain large interfaces/lessons (SRP and ISP 💔)

Adhering to S.O.L.I.D. ideas makes testing simpler and removes the necessity to use mocking frameworks.
By some means Android Builders assume that they’ll’t take a look at their code in the event that they don’t use mocking frameworks, in contrast to iOS builders that have a tendency to remain as vanilla (classicists) as potential.
It is because Java/Kotlin library builders have carried out superb advertising and marketing prior to now years and not as a result of unit testing was born with mocking in thoughts.

My take is that selecting between Mocking Frameworks and Take a look at Doubles is as much as your workforce’s desire (if that’s the solely method your workforce goes to put in writing checks then it’s the proper method) however your code must be testable whatever the mocking technique you utilize!

References:
* Clear Structure, Chapter 10 (ISP)
* Clear Structure, Chapter 13 (CRP)

A software program artifact must be open for extension however closed for modification.

The last word purpose of software program structure is so as to add new options with out having to rewrite or recompile any present code.
It is because each time you modify present code you could introduce regression bugs and break beforehand constructed functionalities, to not point out merge conflicts and really large pull requests!

The OCP is the least impartial S.O.L.I.D. precept and the one it’s best to monitor essentially the most, the truth is, breaking another precept will almost certainly break the OCP too:

  1. When LSP is violated, therefore inheritance is used within the flawed method, including a subclass might require a modification of the guardian class to be able to not break different subclasses.
  2. When SRP is violated, therefore a category belongs to greater than 1 actor, every time an actor requires a brand new integration then that new integration will modify an present methodology which can have an effect on additionally the opposite actors.
  3. When DIP is violated, therefore concrete lessons rely on concrete lessons (creating additionally transitive dependencies), including a brand new methodology someplace would require a recompilation of all of the lessons as much as the beginning of the stream (and sure, if you happen to have been questioning, recompilation counts as modification too).
  4. When ISP is violated, therefore collaborators’ abstractions have extra strategies than required, including a brand new methodology to a collaborator will recompile additionally lessons that don’t use that methodology.

So if I comply with the opposite 4 ideas, do I get this one without spending a dime?

No, sadly.

There are different methods you’ll be able to break the OCP with out breaking the opposite ideas, however fortunately it is vitally straightforward to detect these additional eventualities.

Flag Arguments AKA “Magic Booleans”

What number of instances have you ever thought “I’ll simply add a boolean right here, if true I do A and if false I do B”?

That’s a modification of present code the place you assist the mixing of B by including a boolean.

As a substitute of including a boolean and modifying an present perform, add a brand new perform.

For instance, as a substitute of:

enjoyable pay(isDebitCard: Boolean)  ... 

do:

enjoyable payWithDebitCard()  ... enjoyable payWithCreditCard()  ... 

Enums

They current the identical downside of flag arguments however the probability of breaking present options is even greater since you’ll be able to have quite a lot of values, in contrast to booleans the place you may have at most 2 values (except you might be passing multiple boolean 😈).

So once more, as a substitute of:

enum class PaymentType 
DEBIT_CARD, CREDIT_CARD, BANK_TRANSFER, GOOGLE_PAY
enjoyable pay(paymentType: PaymentType)

do:

enjoyable payWithDebitCard()  ... enjoyable payWithCreditCard()  ... enjoyable payWithBankTransfer()  ... enjoyable payWithGooglePay()  ... 

This fashion including a cost methodology can not break the beforehand carried out cost strategies and doesn’t require recompilation of all of the lessons importing the enum.

Does this imply I can’t ever use flags and enums?

It’s almost unattainable to put in writing software program with out ever utilizing flags and enums so, clearly, not all of your lessons and features will strictly adhere to the OCP.
The essential factor is that when you need to use a flag argument or an enum your perform does solely that if/else or when assertion and nothing else.

References:
* Clear Structure, Chapter 8
* Clear Code, Chapter 3

More Posts