Programming in Visual Basic

Chapter 10
Application Architecture and Design Patterns

Post Graduate Program in Computer Science Engineering
Programming in Visual Basic   •   Post Graduate

Application Architecture and Design Patterns

Introduction

Application architecture is the systematic organization of an application's components, responsibilities, interfaces, data flows, and deployment boundaries. Design patterns are reusable, named solutions to recurring design problems. Together, architecture and patterns provide a disciplined way to construct Visual Basic applications that are understandable, testable, secure, maintainable, and capable of evolving.

In small programs, it is possible to place user-interface code, database operations, validation, and business rules in one form. In postgraduate-level software engineering, however, such an arrangement is recognized as a liability. It creates strong coupling, makes testing difficult, and causes small changes to propagate unpredictably. Architectural design addresses these problems by establishing boundaries; design patterns provide proven techniques for implementing those boundaries.

This chapter focuses on Visual Basic and the .NET platform, especially Windows Forms, WPF, ASP.NET-oriented services, and data-access applications. The principles also apply to Visual Basic class libraries and service-oriented systems.

Learning Objectives

After studying this chapter, a learner should be able to:

  1. Explain the relationship between architecture, design, and implementation.
  2. Compare layered, three-tier, hexagonal, and clean architectural styles.
  3. Apply SOLID principles to Visual Basic design.
  4. Select and implement suitable creational, structural, and behavioral patterns.
  5. Design testable Visual Basic applications using dependency injection and interfaces.
  6. Evaluate architectural trade-offs involving maintainability, performance, security, and deployment.
  7. Analyze a realistic application and justify an architectural solution.

1. Architecture, Design, and Patterns

Architecture describes the system at a high level: its major components, their responsibilities, communication mechanisms, deployment locations, and quality attributes. Design operates at a more detailed level, specifying classes, interfaces, algorithms, and collaborations. Implementation expresses those decisions in code.

A useful distinction is:

  • Architecture: Which major parts exist, and how are they related?
  • Design: How do classes and modules collaborate within those parts?
  • Pattern: What recurring solution can be adapted to a known design problem?
  • Framework: What reusable implementation infrastructure constrains or supports the design?

A pattern is not a ready-made class library. It is a design vocabulary. The same pattern may be implemented differently in a desktop application, a web service, or a background process.

Architecture should be driven by quality attributes as well as functional requirements. Important quality attributes include maintainability, testability, scalability, availability, performance, security, portability, and usability. These attributes often conflict. For example, caching may improve performance but introduce stale data; abstraction may improve testability but increase conceptual complexity.

Layered application architecture Presentation layerForms, pages, controls, view models Application layerUse cases, orchestration, commands Domain layerEntities, rules, value objects Infrastructure layerDatabase, files, network, external services
Layered application architecture showing presentation, application, domain, and infrastructure layers
Figure: Layered application architecture showing presentation, application, domain, and infrastructure layers

Figure: A conventional layered architecture and the responsibilities commonly assigned to each layer.

2. Architectural Styles for Visual Basic Applications

2.1 Monolithic architecture

A monolith is deployed as one unit. A monolithic application is not necessarily badly designed: a modular monolith can contain clear internal boundaries while retaining simple deployment. The principal advantages are straightforward deployment, local calls, and relatively simple debugging. Disadvantages arise when modules become tightly coupled and independent scaling or release is required.

2.2 Layered architecture

Layering groups responsibilities into horizontal levels. A common arrangement is presentation, application or service, domain, and infrastructure. In a strict layered architecture, a layer communicates only with the layer immediately below it. In a relaxed layered architecture, higher layers may use several lower layers, although this can weaken boundaries.

Layering is effective for business information systems because it separates user interaction from business decisions and persistence. It does not automatically guarantee good design. A “domain” layer that merely forwards calls to a database is not a meaningful domain model.

2.3 Three-tier architecture

Three-tier architecture generally distinguishes presentation, business logic, and data tiers. A tier is a physical or deployment boundary, whereas a layer is primarily a logical boundary. A Visual Basic desktop client connected to a database may have three logical layers but only two deployment locations. Confusing these terms can produce incorrect deployment and security assumptions.

2.4 Hexagonal architecture

Hexagonal architecture, also called ports and adapters architecture, places the application core at the center. The core defines ports—interfaces representing required or provided capabilities. Adapters connect databases, user interfaces, message brokers, and external services to those ports.

The central principle is that business logic should not depend directly on technical infrastructure. For example, an order service may depend on IOrderRepository, while SQL Server access is supplied by SqlOrderRepository. A test can supply an in-memory repository.

2.5 Clean architecture

Clean architecture emphasizes dependency direction. Enterprise rules should be independent of user interfaces, databases, frameworks, and external agencies. Typical rings are entities, use cases, interface adapters, and infrastructure. The dependency rule states that source-code dependencies should point inward toward more stable policy.

This style is valuable when an application has substantial business complexity or a long expected life. It may be excessive for a short-lived utility with minimal rules. Architecture should be proportional to risk and complexity.

3. Separation of Concerns and SOLID Principles

Separation of concerns assigns distinct responsibilities to distinct modules. It reduces the number of reasons a module changes and limits the impact of change.

Single Responsibility Principle

A class should have one primary reason to change. A CustomerForm that validates input, calculates discounts, executes SQL, writes audit records, and formats reports has several reasons to change. A better design gives those responsibilities to presentation, application, domain, persistence, and reporting components.

Open–Closed Principle

Software entities should be open for extension but closed for modification. In practice, stable abstractions and polymorphism can permit a new payment strategy without altering the order-processing algorithm. The principle is not a prohibition on all modification; indiscriminate abstraction can be worse than a well-contained change.

Liskov Substitution Principle

Objects of a subtype should be usable wherever the base type is expected without invalidating the client's assumptions. A subtype that throws NotSupportedException for a normal base operation or changes important postconditions often violates this principle.

Interface Segregation Principle

Clients should not be forced to depend on methods they do not use. Several focused interfaces are generally preferable to one large “god interface.” In Visual Basic, interfaces can express capabilities such as IClock, IEmailSender, or IOrderRepository.

Dependency Inversion Principle

High-level policy should not depend directly on low-level details; both should depend on abstractions. Details such as SQL providers, file systems, and HTTP clients are replaceable mechanisms. The application core should depend on interfaces whose meaning is defined by the core.

4. Common Design Patterns in Visual Basic

4.1 Creational patterns

Factory Method delegates object creation to a method or subclass. It is useful when the concrete type depends on configuration or business context.

Public Interface INotifier
    Sub Send(message As String)
End Interface

Public Class NotifierFactory
    Public Shared Function Create(channel As String) As INotifier
        Select Case channel.ToLowerInvariant()
            Case "email" : Return New EmailNotifier()
            Case "sms" : Return New SmsNotifier()
            Case Else : Throw New ArgumentException("Unsupported channel", NameOf(channel))
        End Select
    End Function
End Class

Abstract Factory creates related families of objects, such as controls and themes that must be mutually compatible. Builder constructs a complex object step by step and is useful when many optional values exist. Singleton ensures one instance, but it introduces global state and hidden dependencies. It should be reserved for genuinely unique, thread-safe resources and is often replaced by a dependency-injection container managing a suitable lifetime.

4.2 Structural patterns

Adapter converts one interface into another expected by a client. It is useful when integrating a legacy Visual Basic component or a third-party API. Facade provides a simpler interface over a complicated subsystem, such as a single CheckoutService over inventory, payment, tax, and shipping components. Decorator adds behavior without changing the wrapped object; logging, caching, and authorization are common uses. Proxy controls access to another object and may provide lazy loading, remote access, or protection.

4.3 Behavioral patterns

Strategy encapsulates interchangeable algorithms. A pricing service may receive an IDiscountStrategy. Observer establishes a one-to-many notification relationship, as in .NET events. Subscribers must be managed carefully because long-lived publishers can retain subscribers and cause memory leaks or unexpected work.

Command represents an operation as an object. It supports queues, undo, auditing, and retry policies. Template Method defines an algorithm skeleton and permits selected steps to vary, although composition through strategies is often more flexible. Chain of Responsibility passes a request through handlers such as validation, authorization, and logging. Mediator centralizes communication among related objects, reducing direct dependencies but risking an over-large mediator.

5. Dependency Injection and Testability

Dependency injection supplies an object with the services it needs rather than allowing it to construct those services internally. Constructor injection is usually preferred because it makes required dependencies explicit.

Public Interface IClock
    ReadOnly Property UtcNow As DateTime
End Interface

Public Interface IOrderRepository
    Sub Save(order As Order)
End Interface

Public Class OrderService
    Private ReadOnly repository As IOrderRepository
    Private ReadOnly clock As IClock

    Public Sub New(repository As IOrderRepository, clock As IClock)
        Me.repository = repository
        Me.clock = clock
    End Sub

    Public Sub Place(order As Order)
        If order Is Nothing Then Throw New ArgumentNullException(NameOf(order))
        order.PlacedAtUtc = clock.UtcNow
        repository.Save(order)
    End Sub
End Class

The service is testable because a test can provide a fake clock and an in-memory repository. A test should verify observable behavior rather than private implementation details. For example, it can assert that PlacedAtUtc is assigned and that the repository receives the order.

Dependency injection may be manual or container-based. Manual composition is transparent and often sufficient. A container can manage object graphs and lifetimes, but it can also conceal dependencies and make debugging harder. The container belongs at the composition root—the part of the application that wires concrete implementations to abstractions.

6. Data Access and Transaction Boundaries

Repositories abstract persistence-oriented operations, while a unit-of-work abstraction coordinates a consistent set of changes. Neither pattern should be used merely because it is fashionable. A repository that reproduces every SQL operation without expressing domain intent may add little value.

A transaction should protect an atomic business operation. If stock reduction and order creation must succeed together, they should normally share an appropriate transaction boundary. A transaction does not solve distributed consistency automatically. When multiple independent services are involved, eventual consistency, messaging, compensation, and idempotency may be required.

Visual Basic data-access code should use parameterized commands, deterministic disposal, asynchronous APIs where appropriate, and cancellation support for long-running operations. Database credentials must not be embedded in source code. Connection strings and secrets should be managed through secure configuration mechanisms.

7. Architectural Analysis and Quality Attributes

Architecture can be evaluated using scenarios rather than vague claims. A quality-attribute scenario identifies a stimulus, environment, response, and measurable response criterion. For example: “When 500 simultaneous users submit orders during normal operation, the service shall complete 95% of requests within two seconds.” Such a criterion is more useful than saying the system must be “fast.”

Useful design questions include:

  • Which components change together?
  • Which failures must be isolated?
  • Which data must remain consistent?
  • Which operations require authorization and auditing?
  • What is the expected deployment and scaling model?
  • Which dependencies require substitution during testing?
  • What is the cost of introducing an abstraction?

Performance should be measured. A layered call through in-process interfaces usually has negligible cost compared with database or network latency, but excessive object mapping, inefficient queries, serialization, and unnecessary remote calls can be expensive. Optimization should follow profiling rather than assumption.

8. Worked Design Example: University Course Registration

Consider an illustrative case in which a university wants a Visual Basic application for course registration. Students search sections, request enrollment, and receive confirmation. The system must prevent enrollment beyond capacity, enforce prerequisite rules, record an audit trail, and support both a desktop administrative client and a web-facing service.

Background and problem

An initial prototype places SQL queries and registration rules inside Windows Forms event handlers. It works for demonstrations but duplicates logic across screens. A rule change requires editing several forms, and automated testing is difficult because every test requires a live database.

Analysis

The principal risks are duplicated business rules, concurrent enrollment requests, unauthorized administrative actions, and dependence on a particular user interface. Enrollment capacity is a consistency rule, not merely a display rule. Checking available seats in the client and updating later creates a race condition: two requests may observe the same final seat.

Proposed architecture

A modular layered or hexagonal design is selected:

  • Presentation adapters: Windows Forms and HTTP endpoints.
  • Application layer: RegisterStudent use case, authorization checks, and transaction coordination.
  • Domain layer: Student, CourseSection, prerequisite policy, and enrollment rules.
  • Ports: ISectionRepository, IStudentRepository, IEnrollmentRepository, and IClock.
  • Infrastructure adapters: SQL Server repositories, audit writer, and notification service.

The use case validates the request, loads the required domain objects, asks the section to enroll the student, and commits the operation. The database must enforce a suitable concurrency strategy. One option is an atomic conditional update whose effect is accepted only when a seat remains; another is optimistic concurrency using a version column. The chosen approach must be tested under concurrent requests.

Illustrative domain code

Public Class CourseSection
    Public Property Capacity As Integer
    Public Property EnrolledCount As Integer

    Public Sub Enroll()
        If EnrolledCount >= Capacity Then
            Throw New InvalidOperationException("Section is full.")
        End If
        EnrolledCount += 1
    End Sub
End Class

This code expresses a domain invariant, but it is not sufficient by itself for concurrent database transactions. The persistence operation must ensure that two committed transactions cannot exceed capacity. The domain model and the database consistency mechanism therefore cooperate.

Outcome

The registration rules can be tested without Windows Forms or SQL Server. A second presentation channel can reuse the application use case. Infrastructure can change from SQL Server to another provider without rewriting registration policy. The additional interfaces and mapping code introduce some cost, but the cost is justified by multiple clients, complex rules, and a long expected lifespan.

Learning points

The case demonstrates that architecture should follow risk. The important decision was not simply to use a named pattern; it was to locate policy in the domain and application core, isolate infrastructure, define transaction boundaries, and make concurrency explicit.

9. Security, Governance, and Ethical Considerations

Architecture is also a security control. The principle of least privilege requires each component and database identity to receive only the permissions it needs. Authentication establishes identity; authorization determines permitted actions. These concerns should be enforced at service boundaries, not trusted solely to a disabled or hidden user-interface control.

Sensitive data should be minimized, encrypted in transit, protected at rest where appropriate, and retained only as long as justified. Audit records should be tamper-resistant and should identify the actor, action, time, and relevant object without unnecessarily storing sensitive values.

Secure coding includes input validation, parameterized database commands, safe error handling, dependency updates, secret management, and logging that does not expose credentials or personal data. Accessibility and fairness also matter: architectural choices should not exclude users with disabilities or create unjustified differences in service.

AI-assisted programming can generate Visual Basic code, tests, and documentation, but generated code requires review. Developers remain responsible for licensing, security, correctness, privacy, and architectural coherence. AI should support—not replace—requirements analysis, threat modeling, testing, and professional judgment.

10. Contemporary Developments and Best Practices

Current .NET applications increasingly use asynchronous programming, containerized deployment, automated pipelines, observability, and cloud-hosted services. These technologies do not eliminate foundational patterns. They make explicit concerns such as cancellation, retries, resilience, distributed tracing, configuration, and versioned contracts.

Microservices should not be selected solely because they are fashionable. They introduce network failure, distributed transactions, deployment complexity, monitoring requirements, and operational cost. A modular monolith is often a better starting point when domain boundaries and scaling needs are not yet understood.

Recommended practices are:

  1. Start with business capabilities and quality-attribute scenarios.
  2. Keep domain policy independent of UI and persistence details.
  3. Prefer small, explicit interfaces and constructor injection.
  4. Keep composition at a visible application boundary.
  5. Use patterns to solve demonstrated problems, not to decorate designs.
  6. Define transaction, error, retry, and cancellation behavior explicitly.
  7. Automate unit, integration, security, and performance testing appropriately.
  8. Measure performance and reliability in realistic environments.
  9. Document important architectural decisions and their trade-offs.
  10. Review architecture continuously as requirements and technology change.

Challenges and Limitations

No architecture eliminates complexity. Layering can produce excessive indirection; repositories can obscure useful database capabilities; event-driven designs can make control flow difficult to trace; dependency injection can be overused; and patterns can become accidental complexity. Abstractions also have maintenance costs: every interface, adapter, and mapping layer must be understood and tested.

Architecture diagrams may become obsolete, and quality attributes can be difficult to measure early. Legacy systems may not permit clean separation without incremental migration. A practical strategy is to establish seams around high-risk areas, introduce tests, and refactor toward clearer boundaries while preserving working behavior.

Chapter Summary

Application architecture organizes a system’s major responsibilities, dependencies, deployment boundaries, and quality attributes. Design patterns provide reusable solutions but require contextual judgment. Layered, three-tier, hexagonal, and clean architectures offer different ways to separate policy from presentation and infrastructure. SOLID principles guide maintainable object-oriented design, while dependency injection improves substitutability and testing. Factory, adapter, facade, decorator, strategy, observer, command, and related patterns address recurring construction, integration, and collaboration problems. The university registration case showed why domain rules, transaction boundaries, concurrency, security, and multiple clients must be considered together. Effective Visual Basic architecture is proportional to risk, measurable through quality scenarios, and continuously refined through testing and operational feedback.

Key Terms

Architecture: High-level organization of components, dependencies, and deployment boundaries.

Design pattern: A named, reusable solution to a recurring design problem.

Layer: A logical grouping of responsibilities.

Tier: A physically or operationally separated deployment boundary.

Modular monolith: A single deployable system with strong internal module boundaries.

Hexagonal architecture: An architecture that isolates a core through ports and adapters.

Clean architecture: An architecture that directs dependencies toward stable inner policy.

Separation of concerns: Division of a system into distinct responsibilities.

Dependency injection: Supplying dependencies from outside an object.

Composition root: The location where concrete implementations are assembled.

Factory Method: A technique that delegates object creation.

Adapter: An object that translates one interface into another.

Facade: A simplified interface over a complex subsystem.

Decorator: A wrapper that adds behavior without changing the wrapped type.

Strategy: An interchangeable representation of an algorithm.

Observer: A notification relationship between a publisher and subscribers.

Repository: An abstraction for persistence-oriented access to domain data.

Unit of work: A coordination boundary for a set of related changes.

Idempotency: The property that repeating an operation produces the same intended result.

Quality attribute: A non-functional property such as security, performance, or maintainability.

Review Questions

Short Answer Questions

  1. Distinguish a logical layer from a physical tier.
  2. What problem does dependency injection address?
  3. State the main idea of the Dependency Inversion Principle.
  4. Give two appropriate uses of the Adapter pattern.
  5. Why should a Singleton be used cautiously?

Long Answer Questions

  1. Compare layered architecture, hexagonal architecture, and clean architecture in terms of dependency direction and testability.
  2. Explain all five SOLID principles with Visual Basic-oriented examples.
  3. Discuss the benefits and limitations of repository and unit-of-work abstractions.
  4. Explain how design patterns can improve maintainability while also creating accidental complexity.
  5. Describe a secure and testable architecture for a multi-client Visual Basic business application.

Analytical / Application Questions

  1. A Windows Forms application contains SQL queries, validation, and pricing rules in every form. Propose a refactoring architecture and justify the boundaries.
  2. Two students can register for the final available seat simultaneously. Analyze the failure and propose domain, database, and transaction-level controls.
  3. An organization is considering replacing a modular Visual Basic monolith with microservices. Identify the evidence required and evaluate the principal trade-offs.

Multiple Choice Questions with Answers

Question 1

Which architectural principle requires high-level policy and low-level details to depend on abstractions?

A. Single Responsibility Principle

B. Dependency Inversion Principle

C. Interface Segregation Principle

D. Liskov Substitution Principle

Correct Answer: B. Dependency Inversion Principle

Question 2

In hexagonal architecture, a port is best described as:

A. A database table

B. A user-interface control

C. An interface representing an application capability or dependency

D. A deployment server

Correct Answer: C. An interface representing an application capability or dependency

Question 3

Which pattern converts an existing interface into one expected by a client?

A. Adapter

B. Observer

C. Builder

D. Singleton

Correct Answer: A. Adapter

Question 4

What is the preferred reason for using constructor injection?

A. It hides required dependencies

B. It makes required dependencies explicit

C. It guarantees singleton lifetimes

D. It eliminates the need for interfaces

Correct Answer: B. It makes required dependencies explicit

Question 5

Which pattern encapsulates interchangeable algorithms?

A. Facade

B. Strategy

C. Proxy

D. Mediator

Correct Answer: B. Strategy

Question 6

Why is checking available course capacity only in the client unsafe?

A. It prevents object-oriented design

B. It cannot display data

C. Concurrent requests may observe the same remaining seat

D. It requires a facade

Correct Answer: C. Concurrent requests may observe the same remaining seat

Question 7

Which location should normally assemble concrete implementations for dependency injection?

A. The domain entity

B. The database schema

C. The composition root

D. The value object

Correct Answer: C. The composition root

Question 8

What is a principal risk of an over-large mediator?

A. It may become a central object with excessive responsibility

B. It cannot communicate with objects

C. It always causes database deadlocks

D. It prevents compilation

Correct Answer: A. It may become a central object with excessive responsibility

Question 9

Which statement correctly distinguishes a tier from a layer?

A. A tier is always a class, whereas a layer is always a server

B. A tier is primarily a deployment boundary, whereas a layer is primarily logical

C. They are identical terms in all contexts

D. A layer can contain no business logic

Correct Answer: B. A tier is primarily a deployment boundary, whereas a layer is primarily logical

Question 10

Which practice is most appropriate for database commands in a secure Visual Basic application?

A. Concatenate user input into SQL strings

B. Store passwords in source code

C. Use parameterized commands and secure secret management

D. Rely only on hidden interface controls

Correct Answer: C. Use parameterized commands and secure secret management

Programming in Visual Basic
Chapter 10 of 10