The motivation behind creating this presentation stems from my personal journey implementing Continuous Integration/Continuous Deployment (CI/CD) across various banks in Indonesia since 2016. Throughout this experience, I encountered numerous online resources claiming to set the standard, mandate certain practices, or declare what constitutes best practices in areas such as unit testing, code coverage, and coding standards. Often, these so-called "best practices" proved to be not only expensive but also counterproductive, leading to unnecessary challenges and setbacks. My intention with this deck is to share the insights gained from these experiences, aiming to spare others the frustration and difficulties I faced. By providing a clear and practical explanation of CI/CD processes, I hope to help others navigate these complex practices more effectively, avoiding the pitfalls that can arise from blindly following widespread but misleading advice.
2. Background
The motivation behind creating this presentation stems from my personal journey
implementing Continuous Integration/Continuous Deployment (CI/CD) across various banks
in Indonesia since 2016. Throughout this experience, I encountered numerous online
resources claiming to set the standard, mandate certain practices, or declare what
constitutes best practices in areas such as unit testing, code coverage, and coding standards.
Often, these so-called "best practices" proved to be not only expensive but also
counterproductive, leading to unnecessary challenges and setbacks. My intention with this
deck is to share the insights gained from these experiences, aiming to spare others the
frustration and difficulties I faced. By providing a clear and practical explanation of CI/CD
processes, I hope to help others navigate these complex practices more effectively, avoiding
the pitfalls that can arise from blindly following widespread but misleading advice.
3. Code Repository
A code repository serves as a system for managing versions
of code, with the most widely recognized product for this
purpose being Git. Git organizes code into collections or
sets, and it provides mechanisms for managing branching,
where different versions of the codebase can diverge and
later merge. For server-based code repositories like
Bitbucket, the system functions as a central repository. This
centralization enables multiple contributors to collaborate
more effectively by allowing them to push (upload) their
changes to a common location and pull (download) updates
from others. This structure ensures that all team members
can work on the code simultaneously, while maintaining a
coherent and up-to-date version of the project.
4. Static Test
Static testing evaluates source code against specific
standards to ensure readability and maintainability.
However, adhering strictly to tools like SonarQube's
standard patterns without considering the context may lead
to less maintainable code. The primary aim of static testing
is not just to enforce standards but to make the code easier
to maintain, safer, and more secure. Safety reduces runtime
risks, while security minimizes vulnerabilities. Yet, the
utmost priority is to maintain a balance where code quality
does not compromise ease of development. It's crucial to
critically assess recommendations from tools like
SonarQube; blindly following them can be
counterproductive. In the CI/CD pipeline, during code
reviews, it's essential to prioritize maintainability and
practicality over rigid adherence to any tool's standard
patterns. This approach ensures that static testing
genuinely enhances the development process without
adding unnecessary burdens.
5. Build Process
The build process, pivotal in software development, serves
the fundamental purpose of preparing the application for
delivery, especially for programming languages that
necessitate compilation or transpilation, such as Java or
TypeScript. This phase transforms the source code into an
executable format, ensuring the application is ready for
deployment. However, the unique requirements imposed
by tools like SonarQube introduce a preparatory step before
code scanning can occur. This additional step, while
seemingly inconvenient and rigid, subtly hints at a lack of
foresight by its developers. It is not just highly
recommended but essential that the build result is
packaged as a Docker image for unit testing in the next
subsequent process.
6. Unit Testing
Unit testing, crucial for ensuring an application's overall
functionality, emphasizes testing the application as an entire
system in isolation, rather than isolating its smallest parts. This
approach challenges the conventional use of tools like JUnit,
which are often criticized for promoting a focus on individual
components at the expense of the application's integrated
performance. Modern applications, being complex systems of
interconnected components, demand a testing strategy that
assesses the system as a whole through its interfaces. By
prioritizing interface-based testing—evaluating backend
services via API parameters and outcomes, and frontend
elements through user interactions—this method ensures
comprehensive validation of the application's functionality. It
addresses the core requirement of testing by aligning with real-
world use cases and optimizing resource allocation, thereby
correcting the misguided emphasis on component-level testing
tools like JUnit and similar products.
7. Code Review
Code review signifies the conclusion of the Continuous
Integration (CI) phase, which focuses on integrating changes
into a shared baseline to maintain the integrity and
continuity of the development process. This critical
examination step, conducted through human review before
merge requests are approved, utilizes results from prior
static analysis and dynamic/unit testing. Its goal is to detect
any issues overlooked by automated tests, with a particular
emphasis on code maintainability and quality. Thanks to the
thoroughness of preceding tests, the code review process is
streamlined, designed not to be a bottleneck but a final,
efficient check to further ensure the code's integrity and
maintainability. Conducted ideally by members of the
organization or those with future code stewardship.
8. Image Packaging
The start of the Continuous Deployment (CD) phase begins
with image packaging, a step that closely mirrors the
application build process encountered in the Continuous
Integration (CI) phase. In this step, the application is built
and packaged into an image. However, a distinct feature of
this CD phase step is that it is triggered by a Git tag,
specifically applied to a branch that has previously
undergone review. The purpose of this step is to replicate
the CI build process while simultaneously tagging or
labelling the image for version management and storing it
in an image artifactory. This approach facilitates the
organization and tracking of multiple application versions,
streamlining the deployment process and enabling easier
rollback if necessary. By managing versions in this manner,
the CD process ensures that each deployment can be
performed with precision and that any necessary reversion
to a previous state can be executed swiftly and efficiently,
enhancing the deployment flexibility and operational
resilience of the application.
9. Image Artifactory
An image artifactory acts as a central repository for images
in software deployment and containerization, storing
lightweight, standalone, executable software packages.
These packages encapsulate everything necessary to run a
piece of software—code, runtime, system tools, libraries,
and settings—making them the blueprints for containers.
Containers, in turn, are instances of these images that
execute the application within a virtualized environment
atop the host operating system. The consistency of these
images is paramount for reliable deployments, a
consistency that is preserved even as variables and secrets
(like passwords, tokens, and keys) are introduced. This
approach ensures the application can be tailored and
secured for specific environments—System Integration
Testing (SIT), User Acceptance Testing (UAT), and
Production—without modifying the original image. Such a
strategy facilitates secure, scalable deployments,
maintaining uniformity across different stages of the
development pipeline while enabling customization for
each environment's unique requirements.
10. System Integration Test
Similar to Unit Testing, System Integration Testing (SIT) plays
a crucial role in verifying an application's overall
functionality, particularly focusing on how its various
components interact with each other. SIT involves testing
the integration points between components to ensure they
work together seamlessly, as implied by its name. While it is
possible to mock certain interfaces for these tests,
employing actual systems rather than simulations is
generally more effective for capturing the nuances of real-
world interactions. Although SIT can utilize the same scripts
developed for Unit Testing, automating these tests is highly
recommended to enhance efficiency and reliability. Given
the integrated nature of SIT, the emphasis is on testing
through the most comprehensive interfaces available, such
as the user interface for frontend components and system-
to-system interactions for APIs. This approach ensures a
thorough evaluation of the application's behaviour in a
scenario that closely mimics its operational environment,
prioritizing the endpoints that users and other systems
interact with directly.
11. User Acceptance Test
User Acceptance Testing (UAT) is a critical phase in the
software development lifecycle that ensures the final
product aligns with the end-user's needs and expectations.
Unlike System Integration Testing (SIT), which focuses on
technical integrations, UAT involves stakeholders—users,
initiators, or requestors—in verifying that the application
not only meets technical specifications but also delivers the
intended value and functionality. This process of securing
stakeholder approval is vital, incorporating the use of actual
systems and data simulations to mirror real-world scenarios
accurately. Moreover, the practice of seeking alignment
with stakeholder expectations extends beyond UAT,
permeating every stage of development, including
architecture planning, design, development, and test script
creation. This iterative confirmation across the
development stages ensures the product consistently
reflects the stakeholders' vision, thereby enhancing the
likelihood of its acceptance during UAT.
12. Production
The production environment is where the application
operates in real-time, serving actual users rather than being
used for testing purposes. However, testing within the
production environment, especially after updates, remains
crucial to ensure the application's integrity and security.
This practice, known as production testing or PTR
(Production Test Run), should be conducted swiftly to
minimize disruption. In scenarios where the application's
functionality involves financial transactions, it is advisable
to continue these tests, albeit with minimal monetary
values, to confirm the reliability and security of these
critical operations. The goal is to ensure that actual users
encounter no issues or vulnerabilities that could affect their
experience or compromise their data. This approach
underscores the importance of maintaining a seamless and
secure environment for end-users, even after the
application has been deployed, by identifying and
addressing potential problems before they impact the user
base.
13. Conclusion
This deck synthesizes valuable insights from implementing CI/CD in Indonesia's
banking sector since 2016, critiquing the blind adherence to so-called best
practices found online, such as unit testing, code coverage, and coding
standards, which proved costly and counterproductive. It advocates for a
pragmatic CI/CD approach, emphasizing the importance of effective code
management through Git, balancing static testing without over-reliance on
tools like SonarQube, and the significance of build processes and Docker
packaging for seamless deployment. The deck also highlights the critical roles of
unit testing, code review, and the transition to Continuous Deployment,
underscoring the necessity of image artifactory for version management and
the deployment's operational resilience. It elaborates on System Integration
Testing (SIT) and User Acceptance Testing (UAT) as pivotal for verifying
application functionality and stakeholder alignment, respectively. Lastly, it
stresses the importance of production environment testing to ensure the
application's integrity and security, advocating for a thoughtful, experience-
driven approach to CI/CD to prevent others from facing similar challenges.