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

Database Integration #10

Merged
merged 23 commits into from
Feb 23, 2018
Merged

Database Integration #10

merged 23 commits into from
Feb 23, 2018

Conversation

darahayes
Copy link
Contributor

@darahayes darahayes commented Feb 19, 2018

  • initial connection and setup logic
  • Business logic for inserting records
  • Unit/Integration tests

Update

This PR ended up turning into a mega PR but this is the initial implementation that allows us to store mobile metrics in the database. There are a number of things to be aware of:

  • There is a small amount of validation. The incoming data is parsed into a struct which filters out unnecessary fields. additionally, there are some small checks at the DB layer. ClientId cannot be an empty string and by data field will not accept invalid json. There is no other validation (right now).
  • Right now, the client timestamp is being parsed but not being saved. The event_time field is populated by Postgres at the time of insert.
  • The db_test.go and dao_test.go are actually integration tests. However by default they will not run because of the // +build integration build tag added at the top of those files. You can run integration + unit tests using the standard test command, but also passing the flag tags=integration @maleck13 Not sure how you feel about this. I found this solution in this stackoverflow answer. I know it's not as clear as integration tests having their own folder but it's still fairly obvious by looking at the file that the test is an integration test.
  • Removed references to timescaledb for now (as suggested by @david-martin )
  • Still need to implement some standardised logging approach
  • Docs need updating
  • Code Coverage at a fairly good 87.6%

The original estimate was 4d and I've spent about 3 days on this so far so there's still time to do some housekeeping around docs, build process, release, etc etc.

How to verify

fetch this branch and start the database using docker-compose.

Now start the server using go run cmd/metrics-api/metrics-api.go

You should see a message like

2018/02/22 17:28:30 Starting application... going to listen on :3000

Now you can try out the /metrics endpoint with the following curl request:

curl -X POST \
  http://localhost:3000/metrics \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 76a14738-976f-f0ea-4039-12efeebf4f1e' \
  -d '{
  "clientId": "12345",
  "data": {
  	"app": {
    "id": "com.example.someApp",
    "sdkVersion": "2.4.6",
    "appVersion": "256"
	},
	"device": {
    	"platform": "android",
    	"platformVersion": "27"
	}
  }
}'

You should get a the following response:

{"timestamp":0,"clientId":"12345","data":{"app":{"id":"com.example.someApp","sdkVersion":"2.4.6","appVersion":"256"},"device":{"platform":"android","platformVersion":"27"}}}

Note the timestamp field in the response corresponds to the client timestamp which is currently not being handled or saved.

You can log into the database and verify that the record was created:

psql -U postgres --host localhost
password: postgres

\c aerogear_mobile_metrics
select * from mobileappmetrics;

           clientid           |          event_time           |                                                                      data
------------------------------+-------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------
 org.aerogear.metrics.testing | 2018-02-22 15:14:03.859053+00 | {"app": {"id": "com.example.someApp", "appVersion": "256", "sdkVersion": "2.4.6"}, "device": {"platform": "android", "platformVersion": "27"}}

I'd love to get your reviews @maleck13 @david-martin @wtrocki

@wtrocki
Copy link
Contributor

wtrocki commented Feb 19, 2018

@darahayes Verified that locally. It works for me.
Going to check that once logic will be added.

@coveralls
Copy link

coveralls commented Feb 19, 2018

Pull Request Test Coverage Report for Build #46

  • 35 of 40 (87.5%) changed or added relevant lines in 5 files are covered.
  • 4 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+50.8%) to 87.603%

Changes Missing Coverage Covered Lines Changed/Added Lines %
pkg/mobile/metrics.go 4 6 66.67%
pkg/web/healthzHandler.go 14 17 82.35%
Files with Coverage Reduction New Missed Lines %
pkg/web/router.go 4 77.78%
Totals Coverage Status
Change from base Build #30: 50.8%
Covered Lines: 106
Relevant Lines: 121

💛 - Coveralls

pkg/dao/db.go Outdated
return err
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be worth looking into https://github.com/mattes/migrate for these

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed this is something I mentioned on the previous call. Really this PR is still under heavy development. :)

@paolobueno
Copy link
Contributor

@darahayes I'm basing off of this to just finish exposing the Ping() on the healthz endpoint as a softer introduction to golang 👍

pkg/dao/db.go Outdated
}
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS sdkVersionForClient(clientId varchar(10) not null primary key, version varchar(40) not null, event_time timestamp with time zone)"); err != nil {
return err
}
Copy link

@aliok aliok Feb 20, 2018

Choose a reason for hiding this comment

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

@darahayes @wtrocki I actually found out a much better way for the knowledge base.

Now that we're using Postgres, much more complex aggregations are possible. ElasticSearch had limitations...

We can create a view that contains the latest entries for the "mobileAppMetrics" table (created below) and use that in Grafana.

No need for 2nd insertion/update operation nor a separate table.

But we need to create a view:

create view clientKnowledgeBase as select entry.clientId, entry.event_time, entry.data from mobileappmetrics entry inner join (select clientId, max(event_time) as latestEntryTime from mobileappmetrics group by clientId) latestEntry on entry.clientId = latestEntry.clientId and entry.event_time = latestEntryTime;

Doing select * from clientKnowledgeBase would return the data de-duplicated per client id.

This, however, means that we should use this table only for our metrics, not custom metrics as we would want this view to have entries only for the metrics that needs de-duplication.

I gave this one a try on the POC but haven't checked in anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aliok Sounds really nice. Perhaps you should push it to a branch so we can look at it!

@aliok
Copy link

aliok commented Feb 20, 2018

Note that http endpoint is already implemented part of this task and #14 adds tests for it. #14 is blocked by this PR.

@wtrocki
Copy link
Contributor

wtrocki commented Feb 21, 2018

@darahayes @aliok PR is good to go from my point view.

@darahayes
Copy link
Contributor Author

darahayes commented Feb 21, 2018 via email

paolobueno and others added 9 commits February 22, 2018 09:24
* AEROGEAR-2044 - test metrics endpoint

* AEROGEAR-2044 - use a setup func for creating the server

* AEROGEAR-2044 - use Testify for mocking

* AEROGEAR-2044 - add test case when there is an impl error in MetricsService (http 500)

* AEROGEAR-2044 - use Testify's assertions

* AEROGEAR-2044 - add Testify dep to Gopkg

* AEROGEAR-2044 - fix tests after rebase

* fix: move serialization of metrics data into mobile layer

* AEROGEAR-2044 - use built-in errors

* Add ping endpoint implementation and tests with mocking

* Use `boom` instead of `http.Error`

* Remove generated mock

* Remove extra ping method

* Use testify

* Update gopkg
@wtrocki wtrocki changed the title [WIP] Database Integration Database Integration Feb 22, 2018
pkg/dao/db.go Outdated
func Connect() (*sql.DB, error) {
if db != nil {
return db, nil
// var db *sql.DB
Copy link
Contributor

Choose a reason for hiding this comment

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

Get that guy out :P

func (m *MetricsDAO) Update() {

func (m *MetricsDAO) Create(clientId string, metricsData []byte) error {
_, err := m.db.Exec("INSERT INTO mobileappmetrics(clientId, data) VALUES($1, $2)", clientId, metricsData)
Copy link
Contributor

Choose a reason for hiding this comment

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

[Optional] mobileappmetrics table is used in various SQL calls. DAO here works fine, but maybe it can also handle the actual table creation logic (to fully extract that there is some SQL/postgre involved) Alternative will be to just use constant for mobileappmetrics table name. It's not big deal, since we already have abstraction for SQL queries, but creation of the table seems to be leaking that we using sql.db interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1 I think a constant/config item for the database table is the best option


config := GetConfig()

fmt.Println(config)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit keep tests quiet

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! It's been a long day!


config := GetConfig()

fmt.Println(config)
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

}
}

func TestIshealthyWhenDisconnected(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

TestIshealthyWhenDisconnected -> TestIsHealthyWhenDisconnected

dbHandler := DatabaseHandler{}

err := dbHandler.Connect(config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.SSLMode)
dbHandler.DoInitialSetup()
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be after err check

pkg/dao/db.go Outdated
}
//connection logic
return nil, nil
connStr := fmt.Sprintf("host=%v user=%v password=%v dbname=%v sslmode=%v", dbHost, dbUser, dbPassword, dbName, sslMode)
Copy link
Contributor

Choose a reason for hiding this comment

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

may want to consider looking into setting values for

db.SetMaxOpenConns()
db.SetConnMaxLifetime()

as otherwise you can end up with invalid connections. Have a read of what they do and figure out what makes sense for you

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1 I was reading up on them yesterday but I totally forgot about it today!

pkg/dao/db.go Outdated
return err
}

// assign db variable declared above
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure we need that level of docs on the code 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually an outdated comment from when the db was declared at a package level. Good catch!

@david-martin
Copy link
Contributor

@darahayes I'll give this a spin locally to understand it better, if you don't mind the wait for merge approval

@darahayes
Copy link
Contributor Author

@david-martin no problem at all. The docs may need some work so ping me if you have any questions

}
}

func TestGetConfigCustomEnvVariables(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest looking into table tests. These are heavily used in go. Here is an example https://github.com/aerogear/mobile-cli/blob/master/pkg/cmd/clients_test.go#L37

mock "github.com/stretchr/testify/mock"
)

type MockMetricsService struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

general question, should there be a mock here. Could we inject just a mock dao into the real service here giving us something closer to an e2e test that can run without the db

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would agree with you on this. I think the kind of testing you're suggesting would give us a much greater ROI. However I'm conscious that this code was already reviewed and merged as part of #14. I'm hoping that this PR doesn't grow any larger than it already is.

I would like to see a new issue/ticket created for e2e style testing. The original goal of this ticket was just the database parts but then two other PRs ended up being merged into this branch. (lesson learned - let's avoid doing that again)

@david-martin david-martin self-assigned this Feb 23, 2018
Copy link
Contributor

@wtrocki wtrocki left a comment

Choose a reason for hiding this comment

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

LGTM.
This PR consist of the multiple different PR's that were reviewed. I have reviewed and tested that myself. IMHO It's important to merge that and do verifications with Android and IOS implementations

Copy link
Contributor

@david-martin david-martin left a comment

Choose a reason for hiding this comment

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

Approving this, with the knowledge there may be fixes/changes as part of integration tests with the sdks as covered by https://issues.jboss.org/browse/AGIOS-654 & https://issues.jboss.org/browse/AGDROID-735

@psturc
Copy link
Contributor

psturc commented Feb 26, 2018

@darahayes I've noticed some failures being present in the console output when I run integration tests locally. It's also in the log of circle ci build (make test-integration-cover part). For some reason, it has exit code 0. Is this expected?

@darahayes
Copy link
Contributor Author

@psturc Thanks so much for noticing this. The reason for the failing tests is simple. Now the reason why the exit code was still 0 is a little more unclear. Will fix this very soon.

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

8 participants