Free eBook - Software Architecture with Spring 5.0

3.6 (5 reviews total)
By René Enríquez , Alberto Salazar
  • A new free eBook every day on the latest in tech
  • 30 permanently free eBooks from our core tech library
  1. Software Architecture Today

About this book

Spring 5 and its ecosystem can be used to build robust architectures effectively. Software architecture is the underlying piece that helps us accomplish our business goals whilst supporting the features that a product demands. This book explains in detail how to choose the right architecture and apply best practices during your software development cycle to avoid technical debt and support every business requirement. Choosing the right architecture model to support your business requirements is one of the key decisions you need to take when a new product is being created from scratch or is being refactored to support new business demands. This book gives you insights into the most common architectural models and guides you when and where they can be used. During this journey, you’ll see cutting-edge technologies surrounding the Spring products, and understand how to use agile techniques such as DevOps and continuous delivery to take your software to production effectively. By the end of this book, you’ll not only know the ins and outs of Spring, but also be able to make critical design decisions that surpass your clients’ expectations.

Publication date:
August 2018
Publisher
Packt
Pages
372
ISBN
9781788992992

 

Chapter 1. Software Architecture Today

In this chapter, we will review what software architecture is and why it's still relevant today. We will also discuss the new business demands that have been guiding the world of software development in the last few years, and how they have affected the software industry as a whole.

Software and technology are evolving daily, introducing new demands that businesses must meet in order to remain relevant in a competitive market. Regardless of their core business, every competitive company has had to turn to technology. Online transactions and clients around the world are just some of the challenges that have to be mastered in order to stay ahead.

In order to support these new demands, we have been discovering new ways to do our work. Drastic changes have been made and adopted, directly affecting our software development life cycle (SDLC). Some examples of these changes are reflected in how we work on the following phases:

  • Gathering requirements
  • Organizing teams
  • Designing software architectures
  • Writing code
  • Deploying applications

In this chapter, we will start by revisiting the underlying concepts of software architecture, which have been present for a long time and are still relevant today.

This chapter will cover the following topics:

  • Defining software architecture
  • Common mistakes that are made when creating architectures
  • Architecture and architects
  • Software architecture principles
  • Applying high cohesion and low coupling in order to create components
  • SOLID principles
  • Conway's law
  • Choosing the right technology for you
  • New technology tendencies
 

Defining software architecture


No matter whether or not someone holds the software architect role in a team, every application has an architecture that somebody needs to take care of. This is an important step as it helps us to avoid writing entangled code, which makes a software system impossible to evolve in the future.

First things first: In order to know why you need to remember software architecture, we first need to understand what it is and why it is important.

In software, the term architecture is hard to define. Authors often borrow the definition from the construction industry, which is wrong. Software architecture is not all about diagrams, such as plans for buildings or houses—it's more than that. It's about the shared knowledge that technical and even nontechnical people have about the application that the whole team is creating, how the modules are connected to shape it, and all the complicated and vital elements surrounding it. Good software architectures are heavily focused on business requirements rather than on frameworks, programming languages, diagrams, and programming paradigms. Of course, we need these because we create applications using them. However, they don't have to define the underlying principles that dictate how we conceive the software. Instead, this role should be played according to business requirements.

The long-term success of an application is mainly based on its architecture, which must be created to support a well-defined set of business requirements, as mentioned earlier. Since an application needs to resolve these specific requirements, they must guide the architecture of the application. However, there are two main scenarios in which we guide software architecture decisions based on technology instead of business requirements:

  • I know my land
  • I want to stay ahead

I know my land

This scenario occurs when we create software architectures using frameworks and programming languages that we already know about, without paying close attention to business needs.

Let's say that the ABC company needs an application for manipulating text from large log files. If someone were to ask to work on this requirement, then they will choose a programming language that they are comfortable with during the development process, instead of looking for the best approach elsewhere.

Imagine that the person in charge of creating this application has already mastered JavaScript. In this case, do you think it's a good idea to write code using Node JS or another JavaScript framework running on the server in order to write an application to manipulate log files? I'm not saying that this is impossible—you can do it. However, do you think an application created using this approach will be able to perform and scale better than a system written in Perl, Python, or C, for example? This is not to say that JavaScript is terrible—it is simply important to know that this approach is not a good fit for JavaScript.

I want to stay ahead

We all want to stay ahead with technology, using the latest trends in the programming world to have a better technological background and consequently land cool jobs. Some people tend to write applications, keeping this idea in mind. Let's explain this scenario using the application example for manipulating log files that we mentioned in the previous section.

Suppose you're asked to solve the problem that we mentioned in the I know my land section. In this scenario, your only concern is technology. For instance, let's say you want to try the newest features in the latest PHP release. In this case, you will build this application using PHP. While this programming language has been improving over the last few years since Facebook started to add new features to it, the idea behind writing an application to manipulate large log files using PHP is crazy. As you may know, this programming language is intended to create other kinds of applications—mainly those that have to be accessed using a web browser and without high transactional requirements.

Again, you can write an application using PHP to manipulate large log files, but what will happen when more features are needed? Do you think a software architecture created with this approach in mind will be able to respond quickly to new requirements and the inherent characteristics of the application used in this example?

 

Predicting the future

While we can't predict each detail of an application when we are creating it, we can keep some apparent assumptions in mind to avoid glaring mistakes, like the ones exposed in the preceding sections. Even if you have created an application using the wrong approach, one part of the software architecture process is to evaluate the code base from time to time and take corrective actions based on this. This is important because the existing software architecture needs to evolve in order to avoid becoming useless. During the development process—and because we do not want to miss the established project deadlines—weoften use the FIXMEandTODOtags. However, we should pay close attention to these and take action as soon as we can, as they represent a technical debt that gets worse as time passes. Imagine how easy it is to get rid of a recently introduced debt in the next iteration. Now, imagine how hard it would be if the developer who added that debt is no longer working on the project or even within the same company.

Note

Remember that these tags represent a debt, and debts are paid with interest that increases with time.

The process of improving the existing software architecture sometimes tends to be even more interesting than creating a new one from scratch. This is because you now have more information about the business requirements and how the application was performing at the time that it was in production.

When you are adding new features to an existing application, you will figure out how good the initial idea was. If the process of adding new features is simple and requires only a few changes in its structure, then we can conclude that the software architecture is doing its job well. Otherwise, if we need to make substantial changes to the underlying parts of the original design, we can say that the initial idea and assumptions were all wrong. However, at this point, the team in charge of the product should be responsible enough to make it evolve instead of writing additional patches to support new features.

Even though patching something sounds similar to making it evolve, it isn't. This idea is explained clearly in the book Building Evolutionary Architectures, written by Neal Ford, Rebecca Parsons, and Patrick Kua.

Proactive teams continually apply changes that make it possible to better support preexisting and new features rather than simply sitting and waiting for chaos when things get out of control. There's nothing wrong with changing an initial design, and it's always worth doing this. The following diagram illustrates this process, as applied to a geometric shape:

Evolving original designs

Now that we know that business needs must guide the application architecture, we can conclude that if it is unable to support new features, then new business opportunities will be missed, making the application and its architecture useless.

 

Architecture and architects


Before the agile and DevOps approaches appeared, architects used to focus on creating standards and rules to write code. In the past, it was common to find architects who wrote code, but this approach is currently outdated with regards to programming. Over the last few years, the idea of architects has been disappearing, all thanks to the new emerging models for creating teams. Agile movements have been in the software industry for a while, helping us to rethink how we are building software and organizing teams.

Nowadays, it's almost impossible to find software teams that have an architect working with them. Moreover, the idea of having different groups of people as part of an organization that collaborates using a silo style (where one task has to be finished before starting a new one) is disappearing. A few years ago, we had well-defined roles and even specialized departments for the following roles:

  • Business analysts
  • Developers
  • QA engineers
  • Architects
  • DBAs
  • People working on infrastructure 
  • Operations
  • Security

The following graphic shows how teams work using a silos style:

Teams working as silos

The preceding list also grows in specific cases. Teams working using a silo style used to work on producing defined artifacts, such as documentation, UML diagrams, and other things that are usually incomplete.

This approach is changing, and having small and multidisciplinary teams in charge of taking care of every single detail of an application is now more common. This approach has helped to create proactive teams with strong skills that allow us to ensure that software architecture is still happening all the time.

It's evident that not every team member has the full set of skills required to work on every stage, from gathering requirements to deploying the application in production, but the communication among all of them allows us to reduce the technical gaps and have a better understanding of the bigger picture of the application. This is one of the most important aspects of software architecture.

This shared knowledge helps the team to continue improving the existing software architecture, overcoming the most complex problems. All of the teams in charge of writing software can understand the details of the system under development instead of delegating this responsibility to only one person or even to a department. This approach can lead us to rely on people or teams that would be slightly out of the business context of why the application was being created. This is because people that worked on the project in the past but no longer participate actively due to working on more than one project can't fully understand all of the details of every system.

 

Software architecture principles


Software architecture should improve by following two simple principles that are often difficult to achieve:

  • Low coupling
  • High cohesion

No matter what programming language, paradigm, or tools you are using to architect your applications, these two principles should guide you when building your software architecture components.

In order to build the components that will shape your architecture, it's always worth following the guidelines. These are still relevant, even after many years of existence, and they should always be considered when components are being created. In this section, I'm talking about SOLID principles and Conway's law, which we will discuss in more detail later in this chapter. It is now time to look at what components are in more detail.

Components

A component is a set of functions, data structures, and algorithms that solve one problem. This means that all the code and artifacts that are used to build the component have a high cohesion with each other; the rule here is that the classes or files that create a component should change at the same time and for the same reason.

Software architecture is built using many components, and you should not be worried about having an excessive quantity of these. The more components you write, the more freedom there is to assign them to different developers or even to different teams. Large software architectures can be created using many smaller components that can be developed and deployed independently of each other.

Once we connect these components to each other, they allow us to create the desired software architecture.

As shown in the following diagram, we can see the components as pieces of a puzzle that come together to form an application:

Components forming a larger application

The connected components define application architectures, and their designs describe how each component has been created internally. It's here that pattern designs and SOLID principles must be used to create good designs.

Low coupling

Low coupling refers to the degree to which components depend on each other by their lower structures instead of their interfaces, creating a tight coupling among them. Let's make this easier to understand by using a simple example. Imagine that you need to work on the next user's story:

As a bank customer, I want to receive my bank statement by email or fax in order to avoid having to open the bank application.

As you may discover, the developer should work on two things to solve this problem:

  • Adding the ability to save the user's preferences in the system
  • Making it possible to send the bank statement to the customer by using the requested notification channels

The first requirement seems quite straightforward. To test this implementation, we would use something fairly simple, such as the following code:

@Test 
public void 
theNotificationChannelsAreSavedByTheDataRepository() 
throws Exception 
{ 
  // Test here 
} 

For the second requirement, we will need to read these preferred notification channels and send the bank statement using them. The test that will guide this implementation will look like the following:

@Test 
public void 
theBankStatementIsSendUsingThePreferredNotificationChannels() 
 throws Exception 
{ 
  // Test here 
} 

It is now time to show a tightly coupled code in order to understand this problem. Let's take a look at the following implementation:

public void sendBankStatement(Customer customer) 
{
  List<NotificationChannel> preferredChannels = customerRepository
.getPreferredNotificationChannels(customer);
BankStatement bankStatement = bankStatementRepository
  .getCustomerBankStatement(customer);
preferredChannels.forEach
  (
    channel -> 
    {
if ("email".equals(channel.getChannelName())) 
      {
notificationService.sendByEmail(bankStatement);
} 
      else if ("fax".equals(channel.getChannelName())) 
      {
notificationService.sendByFax(bankStatement);
}
    }
  );
}

 

 

Note how this code is tightly coupled with the implementation of the NotificationService class; it even knows the name of the methods that this service has. Now, imagine that we need to add a new notification channel. To make this code work, we will need to add another if statement and invoke the correspondent method from this class. Even when the example is referring to tightly coupled classes, this design problem often occurs between modules.

We will now refactor this code and show its low-coupled version:

public void sendBankStatement(Customer customer) 
{
  List<NotificationType> preferredChannels = customerRepository
.getPreferredNotificationChannels(customer);
BankStatement bankStatement = bankStatementRepository
.getCustomerBankStatement(customer);
preferredChannels.forEach
  (
    channel ->
notificationChannelFactory
    .getNotificationChannel(channel)
    .send(bankStatement)
  );
}

This time, the responsibility to get a notification channel is passed to the Factory class, no matter what kind of channel is needed. The unique detail that we need to know from the channel class is that it has a send method.

The following diagram shows how the class that sends notifications was refactored to send notifications using different channels and support an interface in front of the implementations per notification channel:

Classes after refactoring

This small but significant change has to lead us to encapsulate the details of the mechanism used to send notifications. This exposes only one well-defined interface that should be used by the other classes.

Although we have shown this example using classes, the same principle is applicable to components, and the same strategies should be used to implement them and avoid coupling among them.

High cohesion

The principle of high cohesion also has a pretty simple definition: one component should perform one and only one well-defined job. Although the description is pretty simple, we often tend to get confused and violate this principle.

In the previous example, we had NotificationService, which was in charge of sending notifications by email and fax. The word and can be helpful for us when it comes to identifying the violation of this principle. Now that we have two different classes (one per notification channel), it's fair to say that our classes only have one responsibility.

Again, the same is true for components, and another reason to keep the same idea with them is that you will likely have each component accomplishing only one specific requirement. For example, what would happen if all our customers just wanted to receive their bank statements by email; do you think it's okay to depend on a class that has the ability to send faxes too?

Although the previous question may seem unimportant, imagine that you solved an existing issue related to sending notifications using faxes as a notification mechanism, and a new issue was then introduced into the mechanism in order to send email notifications by mistake.

Remember that components shape your software architecture, and architects should design them in a way that maximizes team productivity. Aligning your components to the high-cohesion principle is an excellent way to separate them and allows teams to work independently on different parts of your application. This ability to create various components with clear responsibilities will make it easier when solving other issues and adding new features, and will also make you less prone to introducing bugs.

With regards to the previous example, you are probably wondering why the NotificationChannel class is apparently sending notifications with a BankStatement parameter.

Common sense leads us to believe that we need to replace this class with any other generic type. It can be helpful to allow the application to send different kinds of notifications, and not only bank statements: this may include drawbacks, or when a new deposit is received in the account. Even though the idea of supporting incoming requirements looks like something you might want to include in the program at this stage, the application doesn't currently need this ability. This is why it is not necessary for us to add this feature right now. Instead, this design should evolve when this becomes necessary; in this way, we are sticking to the KISS principle (https://www.techopedia.com/definition/20262/keep-it-simple-stupid-principle-kiss-principle) and following the directions of only building the most basic features to make the application work.

 

SOLID principles


SOLID is an acronym that represents the five underlying principles that guide a good software design. The design is related to the creation of components that shape your software architecture.

In 2004, Michael Feathers suggested this acronym to Robert C. Martin, the author of these principles. The process for creating them took him around 20 years, and during this period, many of them were added, removed, and merged to achieve a robust set of principles named SOLID. Let's review each one of the principles and provide a brief and clear explanation that will be helpful for getting a precise idea of how we can use them.

We will use the term module in tandem with the idea of modules shaping components, and we will make reference to the object-oriented programming (OOP)world using terms such as classes and interfaces in order to provide a more precise explanation of modules.

The single responsibility principle (SRP)

The SRP is very closely related to the high cohesion that we reviewed earlier. The idea behind this principle is that a module should be changed for one reason only. 

 

 

This definition leads us to conclude that a module should have only one responsibility. One way to verify whether this principle is achieved in your design is to answer the following questions:

  • Does the module's name represent its exposed functionality?

The answer should be yes. For example, if the module's name refers to the domain, then the module should contain domain classes and some functionality around the domain objects related to the module's name itself. You won't want to have code to support audit elements or any other aspect out of the scope of the module you are working with, for example. If the module is supporting additional features, the code supporting those additional features should probably need to be moved to an existing audit module, or a new audit module should be created.

  • When a new change is required, how many parts of the module are affected?

The answer to this question should be many of them; all classes in the module are highly connected, and a new change will change them for this reason. The desired behavior is prevented from being changed through the exposed interface, but the background implementation is often volatile.

The Open–Closed Principle (OCP)

The OCP is simple to write, but difficult to explain. For this reason, I'll write the following definition first and describe it later:

New features can be added to an existing module by extension and not by modification.

It sounds simple, doesn't it? In order to understand this concept from a practical viewpoint, it is necessary to revisit our last example. Let's check that we are accomplishing this principle by answering the following questions:

  • What do we need in order to support a new notification channel?

We need to write a new class (module), and this should implement an existing interface. Note how the open-closed principle makes sense with the provided answer. To support a new notification channel in our application, we need to create a new class, but we don't need to modify the existing code. According to the previous refactoring that we made, if we needed to support this requirement, we had to adjust the existing service to send notifications.

 

A few questions to validate how well this principle is achieved are as follows:

    • Do I add a new IF statement to my code?

No. If you're looking to add a new feature, you will write a new class instead of modifying an existing one. This is because you are adding and not changing features.

    • How much code do I modify in order to support a new feature?

Hopefully, just a little bit. In a perfect world, you won't need to modify anything, but sometimes a few sections should be changed to support new features in the real world. The rule here is that if you are adding a new feature, your original design should be able to support this requirement with minimal changes. If this is not true, refactoring or changing your initial design is recommended.

    • How big should my source code files be?

Big source code files are a bad idea, and there is no reason for them to be large. If your source code file has hundreds and hundreds of lines, revisit your functions and think about moving code to a new file in order to make the source code files smaller and easy to understand.

    • Should I use abstractions within my code?

This is a tricky one. If you only have one concrete implementation for something, you won't need to have an abstract class or interface. Writing code and inventing new possible scenarios is not desirable at all, but if you have at least two concrete implementations that are related to each other, you have to think about writing an abstraction for them. For example, if we only need to send email notifications, there would be no reason to write an interface for this. However, since we are sending notifications via two different channels, we certainly need an abstraction to deal with them.

The Liskov substitution principle 

The Liskov substitution principle (LSP) has a fancy definition:

Module A can be replaced by module B as long as B is a subtype of A.

Well-defined contracts heavily support this definition and help us reduce the coupling between modules. The following questions can help you figure out how well this principle is achieved:

  • Are the modules interacting using abstractions or concrete implementations?

Here, the answer should be that the modules should not be interacting with either option. There is no reason to establish interactions among modules by using their concrete implementations instead of their interfaces.

  • Should I be casting objects in order to use them?

I hope not. If so, it's because the interface is not well-designed, and a new one should be created to avoid this behavior. The use of the instanceOffunction is also not desirable at all.

  • Is the interaction between modules guided by IF statements?

There is no reason for this to be the case. Your modules should be connected in a way that can be taken care of by the use of an interface and the correct dependency injection to solve their concrete implementations.

The interface segregation principle (ISP)

The principal motivation of the interface segregation principle is aligned with the lean movement where creating values with fewer resources is essential. Here's a short definition for it:

Avoid things that you don't use.

You may have already seen classes (modules) implementing interfaces with some method implementations, such as the following:

public  class Abc implements Xyz 
{ 
  @Override 
  public void doSomething(Param a) 
  { 
    throw new UnsupportedOperationException 
    ("A good explanation here"); 
  } 
  // Other method implementations 
} 
 

Alternatively, another option called comment asimplementation tends to be used, as shown in the following code: 

public  class Abc implements Xyz 
{ 
  @Override 
  public void doSomething(Param a) 
  { 
    // This method is not necessary here because of ... 
  } 
  // Other method implementations 
} 

The preceding examples successfully describe the problem that this principle was created to address. The best way to deal with this issue is by creating more consistent interfaces that conform to the other explained principles. The main problem with this issue is not related to having empty method implementations, but having additional functionality that is not used at all.

Suppose that an application depends on an XYZ library and the system is only using 10% of the available functionality. If a new change is applied to solve an issue that was present in the other 90%, that modified code represents a risk to the part that the application is using, even when it's not directly related to it.

The following questions will help you identify how well you are doing:

  • Do I have empty or silly implementations like the ones mentioned earlier?

Please don't answer YES.

  • Does my interface have a lot of methods?

Hopefully not, as this will make it more difficult to implement all the abstract methods in concrete implementations. If you have many methods, please refer to the next question.

  • Are all the method names consistent with the interface name?

The method names should be consistent with the interface name. If one or more methods don't make sense at all, then a new interface should be created to place them.

  • Can I split this interface into two instead of only one?

If yes, go ahead and do it.

 

  • How many functions am I using from the whole set of exposed functions?

If the modules interacting with an interface are only using a few of the exposed functions, then the other ones should probably be moved to another interface, or even to new modules.

The dependency inversion (DI) principle

It is now time to define the dependency inversion principle:

Modules should depend on abstractions rather than on concrete implementations.

Abstractions represent the high-level details of a module, and the interaction among modules should be done at this level. Low-level details are volatile and ever-evolving. We previously stated that there are no problems with evolved modules, but of course, we don't want to break module interactions because of low-level details, and an excellent way to do this is to use abstractions rather than concrete implementations. The following questions will help you identify how well you are doing:

  • Do I have abstractions as part of my modules?

As discussed earlier in this chapter, many concrete implementations should have an abstraction in front of them. However, when it comes to one specific implementation, this is probably not the case.

  • Am I creating new instances by myself every time?

The answer here should be no. Your framework or mechanism that is in charge of the dependency injection inside your application is responsible for doing this.

 

Conway's law


Mel Conway published a paper in 1968 that is still relevant today, stating the direction that companies should move in. For a long time, we focused on defining rules for everything, such as the following:

  • What time you should arrive at the office
  • The minimum hours that people should work 
  • How many days per week are used for working
  • What type of clothing is appropriate to wear during working hours

 

These rules apply to any type of company, and, in many cases, they are still relevant today. Within the IT world (and particularly the software industry), we created another set of rules to guide our teams (feel free to avoid reading these rules if you don't want to get bored):

  • Business analysts should create use cases with a well-defined structure, allowing the developer to ignore the business details and focus on the technical part of the process.
  • Developers should follow the standard document created by the software architect of the product that was written many years ago.
  • The lines of code written per day should indicate how productive a developer is.
  • When you create a new database object, you have to update the existing trustable database dictionary.
  • As soon as your code is ready to be pushed, use an email template to ask for revision by the QA team. After their approval, repeat this process with the design team and later again with the architecture team.
  • Any change to the pushed code will force you to repeat the process that was explained in the preceding rule.
  • Don't forget UML diagrams when you finish coding your assigned use case. Not all of them are required—only the most important ones, such as those listed here:
    • Class diagram
    • Object diagram
    • Package diagram
    • Component diagram
    • Sequence diagram
    • Deployment diagram

The preceding list of diagrams will be larger in some cases. Fortunately, things have changed nowadays, and crazy processes that force us to write huge documents and create different diagrams that pay no attention are no longer used. With these premises in mind, Mel Conway wrote the following as part of his paper:

"Any organization that designs a system will inevitably produce a design whose structure is a copy of the organization's communication structure."

Conway's thesis is still relevant and has been affecting the way we structure our teams to create successful projects and avoid wasting resources ever since.

 

 

People comprise teams, and the question of how these people should be arranged in order to create successful teams has been answered in many ways in the last few years. All of these answers have suggested building small and multidisciplinary teams that should be small enough to be fed using one pizza and multidisciplinary enough to avoid creating silos during the SDLC.

In this way, companies are promoting a culture of sharing and continuous learning within teams. Teams are continually learning from their successes and failures. They are interacting with each other directly instead of using intermediaries or other protocols of communication.

Business boundaries are defined by teams that allow them to communicate using well-defined interfaces, and since the communication is directly managed by themselves, rapid feedback will enable them to fix issues and take corrective actions when necessary.

 

Choosing the right technology for you


Earlier in this chapter, we defined what software architecture is and what the relevant elements surrounding it are. We also mentioned that frameworks, programming languages, paradigms, and so on are not the underlying elements that should guide your software architecture. Many people defend the idea of deferring as much of your technical decisions as possible in order to have your design open to new options, and that's worth doing. However, you can't postpone these choices forever.

There are a lot of frameworks available on the market. Many of them are new, but old frameworks are also still available. Even at the beginning of the process, when all this stuff is just a detail, you need to carefully choose the framework that you will use to build your software architecture, since this detail will make your life easier (or more difficult) depending on the features that you implement in order to solve the business requirements. I'll show you some considerations that you need to bear in mind when you're deciding which framework to use:

  • How much documentation is available?

This is an important factor to consider. Here, you must think about how much documentation has been written for the vendor, and how many courses are available online (not only by the vendor but by other developers, as well). If you can find books, articles, and showcases, it's always worth exploring these as they will allow you to learn about the tool that you have decided to use.

 

  • How big is the community around your choice?

Having a lot of people working on improving a product is something that you should appreciate. Your choice should be supported not only by the vendor but also by other developers and companies using the product to solve their needs.

  • Is it difficult to write tests using a defined technology that you have in mind?

No matter what your programming style is, you will always benefit from including tests as part of your SDLC. You will also benefit from including tests for another aspect of your software (or at least unit tests, integration tests, functional tests, and load tests). If your framework makes this task difficult, it is better to choose another one. If you are using a framework, ABC, for dependency injection, this should be tested, but if these tests are difficult to write, you won't want to waste your time with them. With this idea in mind, Spring has excellent support for testing, and we'll cover this in subsequent chapters by using a hands-on approach.

  • Can I plug components to add more features?

You are probably thinking "if I want to add a new component, I can simply include a JAR". In some cases, this is true, and in other cases, you'll need to discover a whole set of dependencies for making it work. This is a painful procedure because sometimes you need specific versions of specific libraries, which is more challenging to figure out by ourselves, and this is not something you should spend too much time on. Spring includes Spring Boot, which has an excellent method for adding dependencies to your project in a straightforward way. You should only indicate to Spring that you want to work with JPA (for example) during the application creation process, and Spring itself will be able to figure out all the required dependencies to make it work by itself.

It is common to struggle with Maven a bit when you're looking for the right artifact to bootstrap your application for the first time. The good news with Spring is that you have Spring Initializer, which is a friendly website for bootstrapping your application in a few clicks. You can refer to https://start.spring.io for more details.

 

 

  • What are companies using the product for?

Even when the market is crowded with new tools that look promising, you will not want to gamble when it comes to choosing technologies and frameworks. Before choosing a framework or technology, I encourage you to watch some videos of conferences on YouTube. It would be even better if you can go and attend one of them if you have the chance. You'll also benefit from reading papers, showcases, and case studies about a specific technology, as well as which companies are working with these. You can even start creating analogies based on this information in order to figure out how well a particular technology will fit for you.

However, for many years, I have seen how people have been working with Spring to accomplish their business requirements in different industries.

This framework is mature and is constantly evolving to embrace new programming styles and emerging techniques in the software industry. For example, the latest release of Spring includes support for the most recent features introduced within the Java world and the industry in general, such as reactive programming, the latest Java version, and even support for other programming languages that are becoming popular, such as Kotlin and Groovy.

 

New trends 


In the last few years, a lot of programming languages have been emerging to solve new business requirements, and many of these run on the JVM, which gives a significant advantage to Java developers, making embracing new programming languages less difficult.

It's not a coincidence that new emerging software architectures have been created. Business has expanded around the world, which makes it more challenging to scale old applications. This approach has forced us to rethink how to split business boundaries in order to deliver scalable services to solve business needs. Since we needed to offer services to clients around the world, the cloud appeared, and nowadays we can even select regions to reduce the latency of our applications.

 

 

With the cloud ready to be used, the X appeared as a service paradigm. We now have services that are created to deal with specific requirements, such as online payments, authentication, data storage, and so on. This leads us to the creation of serverless architectures; with these, companies are focusing more on their businesses requirements rather than on details that were solved by other companies and are offered as ready-to-consume services.

Having clients around the world means that there is more data to store, and improved data storage is replacing old relational models. NoSQL was forced to be conceived, and recommended techniques such as normalization have been replaced with these models, making practices and recommendations that were previously good entirely useless now. This movement even forced the creation of new careers around it. We are currently studying this data and making it worthwhile. Data scientists are becoming popular today, and their role is to identify what other business opportunities are hidden behind the data, as well as what actions IT people need to take based on this.

Allowing customers to consume services quickly is the functionality that companies are looking for, and conversational interfaces are guiding us to the right path. Devices that contain software to allow people to establish conversations using their voice (such as Alexa, Cortana, and Siri, among others) are offering new possibilities to consume services easier and faster. SDK tools are currently available for developers in many programming languages, since polyglot developers are the most common nowadays.

Not all businesses need to embrace these new trends. However, these new options are introducing companies to a world of opportunities that will provide them with an advantage over those that are not embracing them.

 

Summary


In this chapter, we looked at the underlying concepts inherent to software architecture. Even when the exposed principles have been in the industry for a while, they are still relevant, and it's worth considering them when working on architectural aspects. Something to remember is that high cohesion and low coupling refers to how you connect your components to shape your software architecture, and the SOLID principles apply to the design of each one of them.

To wrap this up, in this chapter, we have talked about how the software industry is evolving to embrace the new business challenges that companies are currently facing. In the next chapter, we will review what software architecture dimensions are in depth, and we will also learn how to use the C4 model to document software architectures.

About the Authors

  • René Enríquez

    René Enríquez works as technical leader in a multinational company headquartered in Silicon Valley. He worked on different projects using Java Enterprise Edition and Spring Framework. He currently works with different Spring projects to maintain legacy code and write microservices applying best practices to deliver products using Agile techniques with a strong focus on testing at different levels. During the last years, he worked as a software consultant for private and government companies and as an instructor of courses to develop enterprise and mobile applications. He was also a speaker at the ScrumDay and JavaDay conferences in Quito-Ecuador.

    Browse publications by this author
  • Alberto Salazar

    Alberto Salazar is an entrepeneur, passionate Java consultant, JUG leader, Auth0 ambassador and founder of the Java User Group of Ecuador, an associate member of the Java community process and a Java evangelist/trainer. He founded a consulting company in Latin America 10 years ago, where he creates and offers technical solutions based on Java. He has been working for 2 decades creating higly scalable and transactional systems. He is a regular speaker at multiple Java conferences and meetings. He recently organized a Java Conference in Ecuador with Java Champions and co-organized a Java Hackdays event in Spanish that brought together 11 different cities from around the world and 9 Spanish-speaking countries.

    Browse publications by this author

Latest Reviews

(5 reviews total)
Really good book that explains many important concepts of software architecture and clearly shows how they are implemented with Spring
It's great and very useful!

Recommended For You

Hands-On Microservices with Spring Boot and Spring Cloud

Apply microservices patterns to build resilient and scalable distributed systems

By Magnus Larsson
Mastering Spring 5 - Second Edition

Build scalable and flexible Rest APIs and microservices using the latest versions of Spring and Spring Boot

By Ranga Rao Karanam
Java Coding Problems

Develop your coding skills by exploring Java concepts and techniques such as Strings, Objects and Types, Data Structures and Algorithms, Concurrency, and Functional programming

By Anghel Leonard
Hands-On Design Patterns with Java

Understand Gang of Four, architectural, functional, and reactive design patterns and how to implement them on modern Java platforms, such as Java 12 and beyond

By Dr. Edward Lavieri