Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement #3

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Enhancement #3

wants to merge 5 commits into from

Conversation

sanjeevghimire
Copy link
Contributor

created a akka http client to interact with nosql db api.

Copy link

@johanandren johanandren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some feedback!


implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't create a separate actor system for this, instead, make this a class that takes the system and materializer as constructor parameters, and pass in the system of the actor where you use it.


import scala.concurrent.Future

object HttpClient {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call it a "CloudantClient" or possibly a "CloudantHttpClient" instead, as it isn't a generic HTTP client, but rather provides cloudant specific things on top of the http client.

* @param documentType
* @return
*/
def getUrl(documentType: DocumentType.Documenttype): String ={

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an internal method, not a part of the public API of this class, so make it private (or package private if you want to be able to write unit tests for it)

* @param jsonString
* @return
*/
def post(jsonString: String): Future[String] = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the cloudant client does a post/get is internal details which the calling code does not have anything to do with, I think you should raise the abstraction level one step and call these save and load. (Imagine the corresponding if the JDBC API would say sendBytesOverNetworkWithTcp rather than executeQuery)

I still think that accepting strings and producing strings is the wrong abstraction, but one step at a time I guess.

If it ends up with wanting to avoid parsing the JSON (which I think would be the right thing) for performance reasons I would still introduce a new class denoting that it is not any string but raw json, in that case it may make sense to never even parse it into a string but keep it as bytes. Something like case class RawJson(rawData: ByteString).

}else if(documentType.equals(DocumentType.Results)){
url = config.getString("cloudant.get_results_url")
}else{
url = config.getString("cloudant.get_fixtures_url")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading config is somewhat costly, so it makes sense to rather read these once and cache the values in fields of the class when constructing the instance, just like you do with the auth header.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would creating a immutable map like below would work?

class AppConfig(implicit val system: ActorSystem,
                implicit val materializer: ActorMaterializer ) {
  private var config = scala.collection.immutable.Map[String, String]()
  def load(): Unit ={
    val appConfig = ConfigFactory.load();
    config+= ("username" -> appConfig.getString("cloudant.username"))
   ...
  }
  def getConfig(key: String): String ={
    config(key)
  }
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you could use a map, but I think just using fields are more clear and allow you to keep separate types with the values rather than either forcing all configurable values to be strings or

Note that you should not load the configuration yourself, instead you should use it from the actor context or system, so:

class AppConfig(implicit system: ActorSystem, materializer: ActorMaterializer ) {
   // gives us a config just containing the things inside cloudant { ... } in the config file
  private def config = system.settings.config.getConfig("cloudant")
  // these are strings, as for example
  private val username = config.getString("username")
  private val resultsUrl = config.getString("get_results_url")
  // just an example, a boolean setting
  private val useHttps = config.getBoolean("use-https")
... the rest of the class ... 

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ups, of course not private then if you are creating a class that other classes use to know the settings :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside of Akka (and the surrounding projects) we call such classes NnnnSettings and a pretty useful thing is to model them as case classes, like this:

object CloudantSettings {
  def apply(config: Config) = 
    CloudAntSettings(config.getString("username"), ... ... )
  def apply(system: ActorSystem) = 
    apply(system.settings.config.getConfig("cloudant"))
}
case class CloudantSettings(
  username: String, 
  password: String,
  resultsUrl: String,
  ...etc...
)

This makes it easy to create custom instances both programmatically and from config.

*/
def getUrl(documentType: DocumentType.Documenttype): String ={
var url: String = null
if(documentType.equals(DocumentType.TeamTable)){

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect use case for Scala pattern matching, would make this

documentType match {
  case DocumentType.TeamTable => tablesUrl
  case DocumentType.Results => resultsUrl
  case DocumentType.Fixtures => fixturesUrl
}

additionally changing DocumentType into an ADT (algebraic data type), like so:

sealed trait DocumentType
case object TeamTable extends DocumentType
case object Results extends DocumentType
case object Fixtures extends DocumentType

would then give you a compiler error if you at some future point added another document type but forgot to update the pattern match expression to cover all possible values.




}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it can be deleted now

entity <- Unmarshal(response.entity).to[ByteString]
} yield entity

responseEntity.mapTo[String]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right to me, you cannot just typecast a ByteString to a String, you should either Unmarshal a string above if possible, or you would have to .map(byteString => byteString.utf8String) to parse the bytes into a String

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants