Finally, we reached the last chapter of the series. You know how the whole process I went through to design the backend, starting with modeling of the domain; tons of infrastructural detail for persistence, transactions, security, APIs, and gateways; everything packed up and acting like one single engine containerized with Docker; Now it’s time to make usage of it and this post is fully dedicated to frontend/SPA built with Angular.
- Part 1 - Project’s overview and Domain-driven Design
- Part 2 - Strategic and Tactical Design
- Part 3 - Domain events and Event Sourcing
- Part 4 - Implementing Event Sourcing with MartenDB
- Part 5 - Wrapping up infrastructure (Docker, API gateway, IdentityServer, Kafka)
- Part 6 - Angular SPA interacting with API
Why Angular and Typescript
As you may have noticed, I’m mainly a backend / C# developer, so Typescript felt natural at first glance. The first contact with the language after many years working with Javascript only when doing fullstack work was enough to sell it to me. Its similarity with C#, excellent documentation, and community make it a recommended option for backend developers willing to venture into the frontend world; no matter what framework or library I use, if it supports Typescript, that’s where I’ll go with it.
Angular is one of the most popular frameworks for developing modern web applications; I’ve chosen it due to its built-in architectural template (and its excellent CLI) and the native support for Typescript. The framework is opinionated, and there’s an initial learning curve to master its structure and components. This is good because it promotes uniform styling everywhere it’s used, but some may argue it undermines flexibility. On the other hand, frameworks like Vue give you extreme flexibility but not a very opinionated structure, requiring a bit more proficiency and organization to design the app cohesively.
note: If you’re curious about Vue, I built this fun project using it a while ago.
Development tools
Regarding tooling, you can build up an entire web application using a few commands in the CLI,
creating modules, components, services, directives, routing, and everything this framework has to offer. Please check their documentation if you’re interested in learning more.
Lastly, I recommend you to use Visual Studio Code as editor. Its modularity through plugins makes it one of the best, if not the best, code editors you can use for web development. Go to the extensions menu, type anything (including Angular) you need, and get an extension. There are great extensions for code snippets, lint, icons, code prettiers, and anything you need.
By the way, this blog is entirely written using Visual Studio Code! In fact, I started the blog initially to talk about GitMarker an extension for VS Code I built a while ago, and you can follow this series of articles and learn how to build your extension.
Architecture
The frontend architecture is simple and follows what angular suggests. You can learn more about each component by checking the documentation or other architectural patterns. I’ll highlight what matters for us in the structure below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
├── app
│ ├─ core
│ | ├── guards
| | ├── interceptors
| | ├── pipes
| | └── services
├───├─ modules
│ | ├── authentication
│ | └── ecommerce
│ | ├── components
│ | ├── constants
│ | ├── models
│ │ └── services
├───├─ shared
│ | ├── services
│ | └── components
└───
core
This module holds the code that could be used for any modules throughout the application, with generic implementations and nothing specific to underlying modules. Things like middleware for intercepting errors, generic pipes and HTTP services, notifications, local storage, token handlers, etc.
modules
The modules folder is where I meant to place different logical sections of the application. I see the
user account
creation andlogin
as a public section to be exposed without needing authentication, but as a preceding step. I grouped them into theauthentication
module. Everything else assumes the user is authenticated and goes inside theecommerce
module. You can organize your modules as you see fit for your needs.modules.ecommerce
components: I placed all specific components for the e-commerce application here. Notice that although it’s inevitable to follow somehow the bounded contexts we’ve seen before, the front end is always user-oriented. Some components are named or display different terminology than used for their counterparts in the backend, like the
cart
for example, which will act towards handling quotes directly here since I didn’t design `cart’ as part of our aggregates (but we could have, of course). Bear in mind that the front end can be a complex mix of data from different places, touching any backend part exposed through the API. Queries then load View Models or DTOs without necessarily having to mirror all the intricacies of the backend business representation.constants: The constants used throughout this module to avoid magic numbers or strings`*. It’s a best practice for maintainability.
models: These models act more like anemic Typescript classes for the matching view models returned from the API. There’s no behavior added, and I grouped some I’m using only for sending as HTTP request parameters in the
requests
folder.services: Injectable services extending the
RestService
class from the core, exposing endpoints for components to make async calls to the backend API (gateway).shared: Shared is where I’m placing services and components with specific implementations to support the overall UI, from simple things like the loader displayed every time some request is processed or a confirmation dialog to prevent unrevertable actions to the
StoredEventsViewer
component showing the sequence of domain events from any aggregate in a standard way.
Run SPA, run!
The Angular app depends on many third-party packages, and you need to install them. For that, you need to install NodeJs beforehand. All the packages used by the app are listed in the packages.json
, and all you need for installing them is run for the first time you run the app:
1
ng npm install
All these packages will be installed into the node_modules
folder. Notice I’m ignoring the folder from .gitignore
. I want this to be away from the commits. It would be terrible to have to add those in the codebase instead of always getting them fresh from the vendors and updating security patches. You should NEVER include this folder in your project, no matter what JS framework you’re using (trust me, this is happening somewhere out there).
Now, with all set and docker running the backend containers after you hit docker-compose up
or run the docker-compose project from Visual Studio, it’s time to run the Angular SPA and bring the application to the light.
1
ng serve
This command will build the code, transpiling typescript into javascript, and show errors, if any. If it builds with success, you will have the Angular Live Development Server listening to localhost:4200
, and all you need to do is to open http://localhost:4200/ in your favorite browser. There are deployment matters you should consider when going to production, like bundle and budget to optimize the size of the application and consequently increase the load speed. Keep it in mind!
Sending Commands, Querying data
Throughout the former chapters, you’ve seen the role of commands
for triggering changes in the state of our aggregates, while the queries
read persisted data. I also mentioned how the CQRS
pattern is perfect for achieving that naturally, especially when using Event Sourcing.
Most components in the ecommerce
module contain methods performing both read and write operations. For example, after listing all products from the catalog and performing the addQuoteItem
action, a product will be used to create an AddQuoteItemRequest
that contains the productId, quantity, and currencyCode; to finally request a quote using the QuotesService.ts
HTTP rest service to add or change an item into the quote, by hitting the API PUT
endpoint located in the QuotesController.cs:
1
[HttpPut, Route("{quoteId:guid}/items")]
As you can see, Commands are imperative and should not return data, which is a job for queries. Commanding is a one-way operation. I only check if the operation succeeds afterward; or otherwise, the core/interceptors/ServerErrorInterceptor
will automatically display what went wrong in the server. If everything goes well, another request is made to Query the quote with the more updated data:
1
[HttpGet, Route("{customerId:guid}/quote/{quoteId:guid?}")]
With an expected result containing the data, the UI can perform what’s due to display it visually. Everything else in the project works pretty much the same way.
Final thoughts
This wraps up the project as a whole. I’m giving you an out-of-the-box solution to play around with the concepts I just scratched the surface here, with no intention of diving deep into the system design’s complexities for this specific type of application. I hope it will be a great starting point for you on a broader journey, and thank you for reading it until the end.
Please Let your feedback if you have any, and share the series with whoever you think may be useful.
Have a happy coding!