Skip to content

Commit

Permalink
Use Circuit Breakers for Music Store Service interactions
Browse files Browse the repository at this point in the history
[Finishes #153216903]
  • Loading branch information
Tim Hess committed Nov 28, 2017
1 parent 699a445 commit dbb3b0b
Show file tree
Hide file tree
Showing 26 changed files with 415 additions and 211 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -209,3 +209,5 @@ project.lock.json
pyenv/
test.out/
user.ini

git.properties
4 changes: 3 additions & 1 deletion MusicStore/README.md
Expand Up @@ -12,10 +12,12 @@ Note: The OrderService and ShoppingCartService are independent from the Music ap
This application makes use of the following Steeltoe components:
* Spring Cloud Config Server Client for centralized application configuration
* Spring Cloud Eureka Server Client for service discovery
* Steeltoe Connector for connecting to MySql using EFCore
* Steeltoe Connector for connecting to MySql using EFCore
* Steeltoe CircuitBreaker to help prevent cascading failures from lower level service failures
* Steeltoe Management for enabling management actuator endpoints that can be used by the Pivotal Apps Manager
* Optionally uses Steeltoe Redis Connector to connect to a Redis cache for Session storage. Note: This is required if you want to scale the MusicStoreUI component to multiple instances.
* Optionally uses Steeltoe Redis DataProtection provider to the cause the DataProtection KeyRing to be stored in a Redis cache. Note: This is also required if you want to scale the MusicStoreUI component to multiple instances.
* Optionally uses Hystrix Dashboard for monitoring Circuit Breakers

The default is to NOT use a Redis cache for Session storage or DataProtection KeyRing storage. Details on how to enable it are provided below.

Expand Down
6 changes: 3 additions & 3 deletions MusicStore/src/MusicStoreService/MusicStoreService.csproj
Expand Up @@ -16,11 +16,11 @@
<ItemGroup>
<!-- GitInfo provides $(Git*) properties used below -->
<PackageReference Include="GitInfo" Version="2.0.6" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
<PackageReference Include="Pivotal.Extensions.Configuration.ConfigServerCore" Version="2.0.0-dev-00118" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.0.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.0.1" />
<PackageReference Include="Steeltoe.CloudFoundry.Connector.EFCore" Version="2.0.0-dev-00193" />
<PackageReference Include="Steeltoe.Extensions.Logging.DynamicLogger" Version="2.0.0-dev-00031" />
<PackageReference Include="Pivotal.Discovery.Client" Version="2.0.0-dev-00109" />
Expand Down
9 changes: 0 additions & 9 deletions MusicStore/src/MusicStoreService/git.properties

This file was deleted.

@@ -1,16 +1,17 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using MusicStoreUI.Models;
using MusicStoreUI.ViewModels;
using MusicStoreUI.Services;
using MusicStoreUI.Services.HystrixCommands;
using MusicStoreUI.ViewModels;
using Steeltoe.CircuitBreaker.Hystrix;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MusicStoreUI.Areas.Admin.Controllers
{
Expand All @@ -20,19 +21,24 @@ public class StoreManagerController : Controller
{
private readonly AppSettings _appSettings;
private readonly IMusicStore MusicStoreService;
private GenresCommand _genres;
private ArtistsAllCommand _artists;

public StoreManagerController(IMusicStore musicStore, IOptions<AppSettings> options)
{
MusicStoreService = musicStore;
_appSettings = options.Value;
_genres = new GenresCommand(HystrixCommandGroupKeyDefault.AsKey("MusicStoreGenres"), MusicStoreService);
_artists = new ArtistsAllCommand("MuscStoreArtists", MusicStoreService);
}


//
// GET: /StoreManager/
public async Task<IActionResult> Index()
{
var albums = await MusicStoreService.GetAllAlbumsAsync();
var albumsCommand = new AlbumsAllCommand("GetAlbum", MusicStoreService);
var albums = await albumsCommand.ExecuteAsync();
return View(albums);
}

Expand All @@ -44,12 +50,12 @@ public async Task<IActionResult> Index()
{
var cacheKey = GetCacheKey(id);

Album album;
if (!cache.TryGetValue(cacheKey, out album))
if (!cache.TryGetValue(cacheKey, out Album album))
{
album = await MusicStoreService.GetAlbumAsync(id);
var albumCommand = new AlbumCommand("GetAlbum", MusicStoreService, id);
album = await albumCommand.ExecuteAsync();

if (album != null)
if (album != null && !albumCommand.IsResponseFromFallback)
{
if (_appSettings.CacheDbResults)
{
Expand All @@ -75,8 +81,8 @@ public async Task<IActionResult> Index()
// GET: /StoreManager/Create
public async Task<IActionResult> Create()
{
var genres = await MusicStoreService.GetGenresAsync();
var artists = await MusicStoreService.GetAllArtistsAsync();
var genres = await _genres.ExecuteAsync();
var artists = await _artists.ExecuteAsync();

ViewBag.GenreId = new SelectList(genres, "GenreId", "Name");
ViewBag.ArtistId = new SelectList(artists, "ArtistId", "Name");
Expand All @@ -86,15 +92,14 @@ public async Task<IActionResult> Create()
// POST: /StoreManager/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
Album album,
[FromServices] IMemoryCache cache,
CancellationToken requestAborted)
public async Task<IActionResult> Create(Album album, [FromServices] IMemoryCache cache, CancellationToken requestAborted)
{
if (ModelState.IsValid)
{
album.Artist = await MusicStoreService.GetArtistAsync(album.ArtistId);
album.Genre = await MusicStoreService.GetGenreAsync(album.GenreId);

var genreCommand = new GenreCommand("MusicStoreGenre", MusicStoreService, album.GenreId);
album.Genre = await genreCommand.ExecuteAsync();

await MusicStoreService.AddAlbumAsync(album);

Expand All @@ -112,8 +117,8 @@ public async Task<IActionResult> Create()
return RedirectToAction("Index");
}

var genres = await MusicStoreService.GetGenresAsync();
var artists = await MusicStoreService.GetAllArtistsAsync();
var genres = await _genres.ExecuteAsync();
var artists = await _artists.ExecuteAsync();

ViewBag.GenreId = new SelectList(genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(artists, "ArtistId", "Name", album.ArtistId);
Expand All @@ -130,8 +135,8 @@ public async Task<IActionResult> Edit(int id)
{
return NotFound();
}
var genres = await MusicStoreService.GetGenresAsync();
var artists = await MusicStoreService.GetAllArtistsAsync();
var genres = await _genres.ExecuteAsync();
var artists = await _artists.ExecuteAsync();

ViewBag.GenreId = new SelectList(genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(artists, "ArtistId", "Name", album.ArtistId);
Expand All @@ -142,23 +147,21 @@ public async Task<IActionResult> Edit(int id)
// POST: /StoreManager/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(
[FromServices] IMemoryCache cache,
Album album,
CancellationToken requestAborted)
public async Task<IActionResult> Edit([FromServices] IMemoryCache cache, Album album, CancellationToken requestAborted)
{
if (ModelState.IsValid)
{
album.Artist = await MusicStoreService.GetArtistAsync(album.ArtistId);
album.Genre = await MusicStoreService.GetGenreAsync(album.GenreId);
var genreCommand = new GenreCommand("MusicStoreGenre", MusicStoreService, album.GenreId);
album.Genre = await genreCommand.ExecuteAsync();
await MusicStoreService.UpdateAlbumAsync(album);
//Invalidate the cache entry as it is modified
cache.Remove(GetCacheKey(album.AlbumId));
return RedirectToAction("Index");
}

var genres = await MusicStoreService.GetGenresAsync();
var artists = await MusicStoreService.GetAllArtistsAsync();
var genres = await _genres.ExecuteAsync();
var artists = await _artists.ExecuteAsync();

ViewBag.GenreId = new SelectList(genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(artists, "ArtistId", "Name", album.ArtistId);
Expand All @@ -169,7 +172,8 @@ public async Task<IActionResult> Edit(int id)
// GET: /StoreManager/RemoveAlbum/5
public async Task<IActionResult> RemoveAlbum(int id)
{
var album = await MusicStoreService.GetAlbumAsync(id);
var albumCommand = new AlbumCommand("GetAlbum", MusicStoreService, id);
var album = await albumCommand.ExecuteAsync();
if (album == null)
{
return NotFound();
Expand Down Expand Up @@ -214,7 +218,8 @@ private static string GetCacheKey(int id)
[EnableCors("CorsPolicy")]
public async Task<IActionResult> GetAlbumIdFromName(string albumName)
{
var album = await MusicStoreService.GetAlbumAsync(albumName);
var albumCommand = new AlbumCommand("GetAlbum", MusicStoreService, albumName);
var album = await albumCommand.ExecuteAsync();

if (album == null)
{
Expand Down
10 changes: 7 additions & 3 deletions MusicStore/src/MusicStoreUI/Components/GenreMenuComponent.cs
@@ -1,24 +1,28 @@

using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MusicStoreUI.Services;
using MusicStoreUI.Services.HystrixCommands;
using Steeltoe.CircuitBreaker.Hystrix;

namespace MusicStoreUI.Components
{
[ViewComponent(Name = "GenreMenu")]
public class GenreMenuComponent : ViewComponent
{
private GenresCommand _genres;

public GenreMenuComponent(IMusicStore musicStore)
{
MusicStore = musicStore;
_genres = new GenresCommand(HystrixCommandGroupKeyDefault.AsKey("MusicStoreGenres"), musicStore);
}

private IMusicStore MusicStore { get; }

public async Task<IViewComponentResult> InvokeAsync()
{
var genres = await MusicStore.GetGenresAsync();
var genres = await _genres.ExecuteAsync();
return View(genres.Select(g => g.Name).Take(9).ToList());
}
}
Expand Down
11 changes: 6 additions & 5 deletions MusicStore/src/MusicStoreUI/Controllers/HomeController.cs
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Extensions.Options;
using MusicStoreUI.Models;
using MusicStoreUI.Services;
using MusicStoreUI.Services.HystrixCommands;

namespace MusicStoreUI.Controllers
{
Expand All @@ -18,20 +19,20 @@ public HomeController(IOptions<AppSettings> options )
{
_appSettings = options.Value;
}
//

// GET: /Home/
public async Task<IActionResult> Index(
[FromServices] IMusicStore musicStore,
[FromServices] IMemoryCache cache)
{
// Get most popular albums
var cacheKey = "topselling";
List<Album> albums;
if (!cache.TryGetValue(cacheKey, out albums))
if (!cache.TryGetValue(cacheKey, out List<Album> albums))
{
albums = await musicStore.GetTopSellingAlbumsAsync(6);
var albumCommand = new AlbumsTopCommand("TopAlbums", musicStore, 6);
albums = await albumCommand.ExecuteAsync();

if (albums != null && albums.Count > 0)
if (albums != null && albums.Count > 0 && !albumCommand.IsResponseFromFallback)
{
if (_appSettings.CacheDbResults)
{
Expand Down
19 changes: 9 additions & 10 deletions MusicStore/src/MusicStoreUI/Controllers/ShoppingCartController.cs
@@ -1,12 +1,12 @@

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MusicStoreUI.Services;
using MusicStoreUI.Models;
using MusicStoreUI.Services;
using MusicStoreUI.Services.HystrixCommands;
using MusicStoreUI.ViewModels;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MusicStoreUI.Controllers
{
Expand All @@ -22,6 +22,7 @@ public ShoppingCartController(IShoppingCart shoppingCart, IMusicStore musicStore
}

public IShoppingCart ShoppingCartService { get; }

public IMusicStore MusicStoreService { get; }

//
Expand All @@ -43,11 +44,11 @@ public async Task<IActionResult> Index()

//
// GET: /ShoppingCart/AddToCart/5

public async Task<IActionResult> AddToCart(int id, CancellationToken requestAborted)
{
// Retrieve the album from the database
var addedAlbum = await MusicStoreService.GetAlbumAsync(id);
var albumCommand = new AlbumCommand("GetAlbum", MusicStoreService, id);
var addedAlbum = await albumCommand.ExecuteAsync();

// Add it to the shopping cart
var cart = ShoppingCart.GetCart(ShoppingCartService, MusicStoreService, null, HttpContext);
Expand All @@ -64,9 +65,7 @@ public async Task<IActionResult> AddToCart(int id, CancellationToken requestAbor
// AJAX: /ShoppingCart/RemoveFromCart/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveFromCart(
int id,
CancellationToken requestAborted)
public async Task<IActionResult> RemoveFromCart(int id, CancellationToken requestAborted)
{
// Retrieve the current user's shopping cart
var cart = ShoppingCart.GetCart(ShoppingCartService, MusicStoreService, null, HttpContext);
Expand Down

0 comments on commit dbb3b0b

Please sign in to comment.