Skip to main content

Why Software needs an Architecture - Proper method explained ? - Testability, Modifiablity, Readability, Maintainability etc.. All abilities !

Hey Readers, This post is the continuation of the previous post on "Why Software needs an Architecture ?" Before continuing on this post, please read that so that the its going to be more interesting.

In previous post we discussed about the importance of architecture, role of a developer in architecture implementation and a proof through a code using a use case of OTP sender. We have seen a improper code and found what are all the problems it is giving us though it served the purpose of sending OTP.


In this pose we will see on implementing the same in the proper way, considering certain, design patterns like, strategy pattern, factory pattern / Creator Pattern, Manager / Controller Pattern.

Let's us consider,

  1. Testability as major NFR (Non functional requirement) in this post.
  2. Modifiability
  3. Re-Usability
  4. Extendability
  5. Additional / incremental changes ability.
Below is the live IDE for the code to run and test.



Below directory structure can be seen inside the proper folder as shown above

  1. core - contains all the core of the system - controller, managers goes here mainly.
  2. strategy - contains all the strategy classes, that will define the strategy to extend the otp sending to various channels like email, sms (Today), but can also add Whatsapp, telegram, etc later has strategy and no changes in the core


Lets see what is the functionality of various classes in these files !! No fear for the word jargon over there, trust me its going to be very simple.

Just for Fun !


  1. otp.factory.ts
    1. Responsibility: To create the objects of email otp sender strategy and sms otp sender strategy based on the condition sent by the controller (OTPManager) and provide the objects.
    2. Advantages :  
      1.  Follows Creator pattern & Singleton - All advantages of creator pattern applies.
      2. Any point in time in unit testing to mock a object, we need to mock the factory so has to manufacture :-P our own objects ! 
      3. Responsibility of creation should not be given to the business logic classes, so to avoid coupling and modifiability any time. Any time I need to change the reference of the object only in the factory. 
  2. otp.factory.spec.ts
    1. Responsibility : Unit test file that unit test the working of the factory.
    2. Scenarios tested - 
      1. should getInstance method provides the instance of OTPFactory
      2. should getInstance return the object
      3. getGenerator should return the generator object for various configuration option
  3. otpmanager.ts
    1. Responsibility : To be the controller that handles the entire use case. It co-ordinates with other units in the code and generate the strategy object and invoke the strategy to send the otp. It also helps in sending the configuration that the user is willing. Also follow Singleton pattern
    2. Advantages:
      1. All the advantages of Controller pattern applies here
      2. Coordinator helps in getting the things done and makes modifiable more easy just by changing the flow.
      3. Abstracts the business logic and other structural logic to keep the boundary maintained.
      4. Handles the errors and exceptions in the single place and more reliable.
  4. otpmanager.spec.ts
    1. Responsibility : Unit test file that unit test the working of the otpmanager.
    2. Scenarios tested - 
      1. sendOtp should send the sms and return true
      2. sendOtp should send the email and return true
      3. sendOtp should send the sms & email and return true
  5. securityManager.ts
    1. Responsibility : Maintains all the crypto algorithms to encrypt and decrypt the otp. Later this manager can be used for enforcing the SSL certificate and much more.
    2. Advantages: 
      1. Single responsibility principle taking care of the security needs in one place
      2. Follows the singleton pattern
      3. Provides the re-usability of the encryption and decryption through out the project.
      4. Provides the modifiable nature to change the security algorithms in one place and that get applied every where with out much changes.
  6. securityManager.spec.ts
    1. Responsibility : Unit test file that unit test the working of the securityManager.
    2. Scenarios tested - 
      1. encrypt and decrypt should work properly 
  7. util.ts
    1. Responsibility : Any re-usable util methods will go here. In our use case, to generate the OTP of 4 digit it works.
    2. Advantages: 
      1. Its main advantage is re-usable and modifiable at one place.
  8. util.spec.ts
    1. Responsibility : Unit test file that unit test the working of the securityManager.
    2. Scenarios tested - 
      1. generateRawOtp should return some random number of 4 digit in string format 
Below is the code if you would like to take a look if not click here to jump to the next section



Let us now start with the strategy folder

    1. email.otpsender.ts
      1. Responsibility: This is the strategy class that implements the logic to send the otp through mail. Initialization of the mail server communication and sending the mail are all handled in this.
      2. Advantages :  
        1. Follows the Strategy pattern and have all its advantages.
        2. Provides the isolation of channel logic away from the business logic.
        3. Provides the re-usability of strategy (email channel) for various purpose independently also.
    2. email.otpsender.spec.ts
      1. Responsibility : Unit test file that unit test the working of the email.otpsender strategy class.
      2. Scenarios tested - 
        1. Should return status as sent if the email is success
        2. Should return error if email is not sent successfully
        3. generateOTP() method should return the encrypted OTP
        4. getTransporter() method should return the Mail object
    3. sms.optsender.ts
      1. Similar to the above email strategy, this is for the email strategy. 
      2. Advantages: 
        1. Even though I did not implement the logic to send the sms in this example, I have the benefit of switching off on any time, now this acts like a stub. This is one of the power of isolating as strategies.
    4. sms.optsender.spec.ts
      1. Responsibility : Unit test file that unit test the working of the sms.optsender strategy class.
      2. Scenarios tested - 
        1. Send should get rejected saying "Method not implemented"
Below is the code if you would like to take a look if not click here to jump to the next section



Comparing the previous post to the solution here,

We can clearly observe that more testable and more reliable and modifiable nature is obtained to the same simple use case just by modifying the way the code units work.

Now the code has become more testable and other non functional requirements got added,

Now we are able to test for various scenarios as mentioned above, if OTP (One Time Password) generated or not, mail sent or not, mail server error handled or not etc.

Hope this post would have placed a good level of impact in the minds on how much the architecture is important to the software and how much it smooth-ens our life and SDLC (Software development life cycle) a lot. Lets us all think in the different perspective of seeing the code and try to arrange the code in more fruitful ways. Lets explore more and more design patterns and architectural patterns since patterns are the experience proven solutions given by by our software ancestors and experts to the common problems faced.

Happy coding and software !! Stay tuned for more interesting posts.

Comments

Popular posts from this blog

How to access the each view of item by position in Recycler View in android ?

How to access the each view of item by position in Recycler View in android ? There are some methods to access the view of each item view to change or update the view during the run time. To access the view of the item view , consider the Recycler view as below, RecyclerView mainRecyclerView = (RecyclerView)view.findViewById(R.id.main_recycler_view); RecyclerAdapter adapter = new RecyclerAdapter(mContext); mainRecyclerView.setAdapter(adapter); main.setLayoutManager(new LinearLayoutManager(mContext));  To access the itemView of each position the following functions can be used,  1. View itemView = mainRecyclerView.getChildAt(positionOfItem);  2. View itemView = mainRecyclerView.findViewHolderForAdapterPosition(Position).itemView;  3. View itemView = mainRecyclerView.findViewHolderForPosition(Position).itemView;  4. Long itemId = mainRecyclerView.getAdapter().getItemId(position);       View itemView = mainRecyclerView.findViewHolderForItemId(itemId);  5. View

A.P.I call or async await not working in Array forEach ?

H ello Readers, Welcome back. You would have wondered why does forEach function on the array does not wait for any asynchronous function even if we provide the async await in the node js. If you are the person, wondered about this ever, this post is right for you. Non working example : Lets consider the below snippet, this will not wait for the asynchronous process to wait. I am making a setTimeout to mock the API call async process. This will result the count as, Output : count = 0 OMG !! Why it doesn't work ? Answer probably might be lying inside the Array prototype from JavaScript. Lets take a look inside the "Array.prototype.forEach" function. From the snippet its clear that, the for loop in which they call the callback function does not wait for the asynchronous process to run in the callback. So this forEach is not build for asynchronous process itself. So can't I use forEach any more for running a asynchronous function ? Answer g