Last updated on

Music streaming service

Points
40 points
Topics
Futures, Modularity, Specifications, Refactoring
Files to submit
src/main/scala/provider/RacingProvider.scala
src/main/scala/provider/SearchProvider.scala
src/main/scala/provider/PlaylistResolver.scala

Introduction

Here is a representation of the architecture of the system:

Architecture

The system has two main components:

The songs are split across two databases (one in Europe, one in the US), with some songs on both. When a key is present in both databases, its contents are the same in both.

Clients send requests to a provider to retrieve a song, and the provider then retrieves the song and serves it to the client.

As the databases are located on different continents, the latency between the providers and the databases is variable. So, when a client requests a song that is available in both databases, we want the first available result to be returned to the client, without waiting for the other database.

Some important types used by the system are shown below:

Part 1: Racing provider (15 pts)

The current implementation of the simplest provider, RacingProviderSync, uses the old synchronous interface. This prevents it from returning songs quickly to users.

Your task in this part is to implement the RacingProvider class in src/main/scala/provider/RacingProvider.scala without using the deprecated (synchronous) methods of the MusicDatabase object:

class RacingProvider[Key, Entry](
    val providerEU: Provider[Key, Entry],
    val providerUS: Provider[Key, Entry]
) extends Provider[Key, Entry]:

  def retrieve(key: Key): Future[Entry] =
    ???

  def retrieveKeys: Future[Set[Key]] =
    ???

src/main/scala/provider/RacingProvider.scala

Use testOnly -- provider.RacingProviderTest.* to test your implementation.

You may find functions firstCompletedOf and firstSuccessOf in src/main/scala/provider/ProviderUtils.scala useful.

Part 2: Playlist provider (10 pts)

We now want to add a new feature: a playlist system. The idea is that users can create playlists, which contain a list of MusicKeys. For this, we need:

We can expose this resolution facility as a provider: given a playlist key, it returns a playlist whose items are MusicEntrys instead of MusicKeys:

class PlaylistResolver(
    musicProvider: MusicProvider,
    playlistProvider: PlaylistProvider[MusicKey]
) extends PlaylistProvider[MusicEntry]:
  def retrieve(key: PlaylistKey): Future[List[MusicEntry]] =
    ???

  def retrieveKeys: Future[Set[PlaylistKey]] =
    ???

src/main/scala/provider/PlaylistResolver.scala

Implement the PlaylistResolver class in src/main/scala/provider/PlaylistResolver.scala.

Use testOnly -- provider.PlaylistResolverTest.* to test your implementation.

  • You can draw inspiration from the PlaylistResolverSync that an intern in your company wrote during an internship.

  • You can use the Future.sequence function to transform an Iterable[Future[T]] into a Future[Iterable[T]], or the Future.traverse function for more complex iteration.

Part 3: Search provider (15 pts)

We now want to add a new search feature to the service. The idea is that users can search for songs by title, artist, album, etc. The providers should then be able to retrieve the songs from the databases, and return them to the client.

Ideally, this feature would be provided by the databases themselves, but their implementation is out of our control. Hence, the team has decided to implement the search feature at the provider level instead. The new SearchProvider should implement the search feature by filtering the results of retrieving all songs with their keys:

class SearchProvider[Key, Entry](p: RacingProvider[Key, Entry])
    extends Provider[SearchQuery[Entry], Set[Entry]]:
  def retrieve(query: SearchQuery[Entry]): Future[Set[Entry]] =
    ???

  def retrieveKeys: Future[Set[SearchQuery[Entry]]] =
    throw UnsupportedOperationException(
      "Cannot retrieve keys of search provider"
    )

src/main/scala/provider/SearchProvider.scala

Implement the SearchProvider class in src/main/scala/provider/SearchProvider.scala.

  • You can draw inspiration from the SearchProviderSync that an intern in your company wrote during an internship, but make sure that you do not unnecessarily wait or sequence operations that don’t depend on each other!

Use testOnly -- provider.SearchProviderTest.* to test your implementation.