Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ make lint
# Or: golangci-lint run ./...

# Run specific test
go test -v -run TestResourceSearch ./internal/usecase/
go test -v -run TestResourceSearch ./internal/service/
```

### Docker Operations
Expand All @@ -64,7 +64,7 @@ This service follows clean architecture principles with clear separation of conc
- `model/`: Core business entities (Resource, SearchCriteria, AccessCheck)
- `port/`: Interfaces defining contracts (ResourceSearcher, AccessControlChecker)

2. **Use Case Layer** (`internal/usecase/`)
2. **Service Layer** (`internal/service/`)
- Business logic orchestration
- Coordinates between domain and infrastructure

Expand Down Expand Up @@ -93,7 +93,7 @@ This service follows clean architecture principles with clear separation of conc

1. HTTP request β†’ Goa generated server (`gen/http/query_svc/server/`)
2. Service layer (`cmd/query_svc/query_svc.go`)
3. Use case orchestration (`internal/usecase/resource_search.go`)
3. Use case orchestration (`internal/service/resource_search.go`)
4. Domain interfaces called with concrete implementations
5. Response formatted and returned through Goa

Expand Down
85 changes: 80 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The implementation follows the clean architecture principles where:
β”‚ β”œβ”€β”€ domain/ # Domain logic layer
β”‚ β”‚ β”œβ”€β”€ model/ # Domain models and entities
β”‚ β”‚ └── port/ # Domain interfaces/ports
β”‚ β”œβ”€β”€ usecase/ # Business logic/use cases layer
β”‚ β”œβ”€β”€ service/ # Business logic/use cases layer
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Rename to Service layer looks good; verify no lingering "usecase" references.

The directory rename aligns with the refactor. Please confirm there are no stale references to internal/usecase across docs and code.


🏁 Script executed:

#!/bin/bash
# Find lingering references to the old "usecase" layer
rg -nP -C2 '\binternal/usecase\b|\bUse\s*Case\b'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for any lingering β€œusecase” references across code and docs
rg -inP -C2 '\busecase\b|use[\s_-]*case\b' .

Length of output: 363


Update stale β€œUse case” reference in CLAUDE.md

  • CLAUDE.md:96 still reads β€œUse case orchestration”; rename to β€œService orchestration” (or β€œService layer orchestration”) to match the updated directory naming.
πŸ€– Prompt for AI Agents
In README.md around line 28, the documentation still refers to "Use case
orchestration" which is now stale; update the reference in CLAUDE.md line 96 to
read "Service orchestration" (or "Service layer orchestration") to match the
directory name change, and ensure any other occurrences in README.md or
CLAUDE.md are renamed consistently.

β”‚ β”œβ”€β”€ infrastructure/ # Infrastructure layer
β”‚ └── middleware/ # HTTP middleware components
└── pkg/ # Shared packages for internal and external services
Expand All @@ -47,10 +47,10 @@ The implementation follows the clean architecture principles where:
- **ResourceSearcher Interface**: Defines the contract for resource search operations
- **AccessControlChecker Interface**: Defines the contract for access control operations

### Use Case Layer (`internal/usecase/`)
### Service Layer (`internal/service/`)

- **Business Logic**: Application-specific business rules and operations
- **Use Case Orchestration**: Coordinates between domain models and infrastructure
- **Service Orchestration**: Coordinates between domain models and infrastructure

### Infrastructure Layer (`internal/infrastructure/`)

Expand All @@ -62,6 +62,10 @@ The OpenSearch implementation includes query templates, a searcher, and a client

The NATS implementation consists of a client, access control logic, and request/response models for messaging and access control.

#### Clearbit Implementation

The Clearbit implementation provides organization search capabilities using the Clearbit Company API. It includes a client for API communication, searcher for organization queries, and configuration management for API credentials and settings.

## Dependency Injection

Dependency injection is performed in `cmd/main.go`, where the concrete implementations for resource search and access control are selected based on configuration and then injected into the service constructor.
Expand Down Expand Up @@ -106,15 +110,21 @@ SEARCH_SOURCE=mock ACCESS_CONTROL_SOURCE=mock go run cmd/main.go
SEARCH_SOURCE=mock ACCESS_CONTROL_SOURCE=mock go run cmd/main.go -p 3000
```

#### With OpenSearch and NATS
#### With Production Services

```bash
# Using OpenSearch and NATS (production-like setup)
# production-like setup
SEARCH_SOURCE=opensearch \
ORG_SEARCH_SOURCE=clearbit \
ACCESS_CONTROL_SOURCE=nats \
OPENSEARCH_URL={{placeholder}} \
OPENSEARCH_INDEX=resources \
NATS_URL{{placeholder}} \
CLEARBIT_CREDENTIAL=your_clearbit_api_key \
CLEARBIT_BASE_URL=https://company.clearbit.com \
CLEARBIT_TIMEOUT=30s \
CLEARBIT_MAX_RETRIES=5 \
CLEARBIT_RETRY_DELAY=2s \
go run cmd/main.go
```
Comment on lines +113 to 129
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix NATS_URL typo in production example.

Missing '=' will break copy/paste.

 OPENSEARCH_INDEX=resources \
-NATS_URL{{placeholder}} \
+NATS_URL={{placeholder}} \
 CLEARBIT_CREDENTIAL=your_clearbit_api_key \
 CLEARBIT_BASE_URL=https://company.clearbit.com \
 CLEARBIT_TIMEOUT=30s \
 CLEARBIT_MAX_RETRIES=5 \
 CLEARBIT_RETRY_DELAY=2s \
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### With Production Services
```bash
# Using OpenSearch and NATS (production-like setup)
# production-like setup
SEARCH_SOURCE=opensearch \
ORG_SEARCH_SOURCE=clearbit \
ACCESS_CONTROL_SOURCE=nats \
OPENSEARCH_URL={{placeholder}} \
OPENSEARCH_INDEX=resources \
NATS_URL{{placeholder}} \
CLEARBIT_CREDENTIAL=your_clearbit_api_key \
CLEARBIT_BASE_URL=https://company.clearbit.com \
CLEARBIT_TIMEOUT=30s \
CLEARBIT_MAX_RETRIES=5 \
CLEARBIT_RETRY_DELAY=2s \
go run cmd/main.go
```
OPENSEARCH_INDEX=resources \
NATS_URL={{placeholder}} \
CLEARBIT_CREDENTIAL=your_clearbit_api_key \
πŸ€– Prompt for AI Agents
In README.md around lines 113 to 129, the production example has a typo in the
NATS URL environment variable ("NATS_URL{{placeholder}}" missing the '=').
Update that line to include the equals sign so it reads
"NATS_URL={{placeholder}}" to ensure the env var is set correctly for copy/paste
usage.


Expand All @@ -124,6 +134,10 @@ go run cmd/main.go

- `SEARCH_SOURCE`: Choose between "mock" or "opensearch" (default: "opensearch")

**Organization Search Implementation:**

- `ORG_SEARCH_SOURCE`: Choose between "mock" or "clearbit" (default: "clearbit")

**OpenSearch Configuration:**

- `OPENSEARCH_URL`: OpenSearch URL (default: `http://localhost:9200`)
Expand All @@ -140,6 +154,14 @@ go run cmd/main.go
- `NATS_MAX_RECONNECT`: Maximum reconnection attempts (default: "3")
- `NATS_RECONNECT_WAIT`: Time between reconnection attempts (default: "2s")

**Clearbit Configuration:**

- `CLEARBIT_CREDENTIAL`: Clearbit API key (required for organization search)
- `CLEARBIT_BASE_URL`: Clearbit API base URL (default: `https://company.clearbit.com`)
- `CLEARBIT_TIMEOUT`: HTTP client timeout for API requests (default: "10s")
- `CLEARBIT_MAX_RETRIES`: Maximum number of retry attempts for failed requests (default: "3")
- `CLEARBIT_RETRY_DELAY`: Delay between retry attempts (default: "1s")

Comment on lines +157 to +164
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Verify Clearbit default values match code.

Ensure docs and internal/infrastructure/clearbit/config.go are consistent for timeout, retries, and delay.


🏁 Script executed:

#!/bin/bash
# Confirm documented defaults vs. code
rg -nP -C2 '(TIMEOUT|RETRY|DELAY|CREDENTIAL|BASE_URL)' internal/infrastructure/clearbit/config.go

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Locate Clearbit config file(s)
fd config.go -t f | grep -i clearbit
# Inspect default values in the located file(s)
fd config.go -t f | grep -i clearbit | xargs -r rg -nP -C2 'Default.*|Base_URL|Timeout|Retries|Delay'

Length of output: 1305


Update Clearbit timeout default in README. Change CLEARBIT_TIMEOUT default from β€œ10s” to β€œ30s” in README.md (currently lines ~157–164) to match DefaultConfig() in internal/infrastructure/clearbit/config.go:37. All other defaults (Base URL, MaxRetries, RetryDelay) already align.

πŸ€– Prompt for AI Agents
In README.md around lines 157 to 164, the documented default for
CLEARBIT_TIMEOUT is incorrect ("10s"); update it to "30s" so it matches the
DefaultConfig() in internal/infrastructure/clearbit/config.go (around line 37).
Change the bullet for CLEARBIT_TIMEOUT to show default: "30s" and ensure no
other formatting or wording changes are made.

**Server Configuration:**

- `-p`: HTTP port (default: "8080")
Expand Down Expand Up @@ -184,6 +206,59 @@ GET /query/resources?name=committee&type=committee&v=1
}
```

## Clearbit API Integration

The service integrates with Clearbit's Company API to provide enriched organization data for search operations. This integration allows the service to fetch detailed company information including industry classification, employee count, and domain information.

### Clearbit API Setup

#### 1. Obtain API Credentials

To use Clearbit integration, you need to obtain an API key from Clearbit:

1. Sign up for a Clearbit account at [https://clearbit.com](https://clearbit.com)
2. Navigate to your API settings to generate an API key
3. Copy the API key for use in your environment configuration

#### 2. Configure Environment Variables

Set the required environment variables for Clearbit integration:

```bash
# Required: Clearbit API key
export CLEARBIT_CREDENTIAL=your_clearbit_api_key_here

# Optional: Custom configuration (defaults shown)
export CLEARBIT_BASE_URL=https://company.clearbit.com
export CLEARBIT_TIMEOUT=30s
export CLEARBIT_MAX_RETRIES=3
export CLEARBIT_RETRY_DELAY=1s

# Set organization search source to use Clearbit
export ORG_SEARCH_SOURCE=clearbit
```

#### 3. API Usage and Features

The Clearbit integration supports the following search operations:

**Search by Company Name:**
- Searches for companies using their registered business name
- Falls back to domain-based search for additional data enrichment

**Search by Domain:**
- More accurate search method using company domain names
- Provides comprehensive company information

#### 4. Error Handling

The Clearbit integration includes robust error handling:

- **404 Not Found**: Returns appropriate "organization not found" errors
- **Rate Limiting**: Automatic retry with exponential backoff
- **Network Issues**: Configurable retry attempts with delays
- **API Errors**: Proper error propagation with context
Comment on lines +254 to +260
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Confirm retry strategy; align wording if not exponential backoff.

Docs promise β€œAutomatic retry with exponential backoff.” Verify implementation matches (e.g., backoff algorithm and jitter). If linear or fixed, adjust wording.


🏁 Script executed:

#!/bin/bash
# Inspect Clearbit client for retry/backoff strategy
rg -nP -C3 '(retry|backoff|exponential|jitter)' internal/infrastructure/clearbit/

Length of output: 3119


🏁 Script executed:

#!/bin/bash
# Inspect Clearbit client for backoff logic by finding sleep calls
rg -nP -C3 'Sleep' internal/infrastructure/clearbit/

Length of output: 0


#!/bin/bash
# Locate and display the Clearbit searcher implementation to inspect retry/backoff logic
fd -e go searcher.go internal/infrastructure/clearbit
sed -n '1,200p' internal/infrastructure/clearbit/searcher.go

Update documentation to reflect fixed retry delays
The implementation uses a constant RetryDelay (default 1 s) across MaxRetries (default 3) with no exponential backoff or jitter. In README.md replace β€œAutomatic retry with exponential backoff” with wording like β€œAutomatic retry with configurable fixed delays.”

πŸ€– Prompt for AI Agents
In README.md around lines 254 to 260, the documentation incorrectly claims the
Clearbit integration uses exponential backoff for retries; update the phrasing
to state that retries use configurable fixed delays (default RetryDelay = 1s)
across MaxRetries (default 3) and do not include exponential backoff or jitter.
Replace the bullet "Rate Limiting: Automatic retry with exponential backoff"
with wording such as "Rate Limiting: Automatic retry with configurable fixed
delays" and, if desired, add a short parenthetical to mention defaults
(RetryDelay = 1s, MaxRetries = 3).


### Testing

The clean architecture makes testing straightforward:
Expand Down
2 changes: 1 addition & 1 deletion charts/lfx-v2-query-service/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ apiVersion: v2
name: lfx-v2-query-service
description: LFX Platform V2 Query Service chart
type: application
version: 0.2.5
version: 0.4.0
appVersion: "latest"
142 changes: 6 additions & 136 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,15 @@ import (
"context"
"flag"
"fmt"
"log"
"log/slog"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"

querysvcapi "github.com/linuxfoundation/lfx-v2-query-service/cmd/query_svc"
"github.com/linuxfoundation/lfx-v2-query-service/cmd/service"
querysvc "github.com/linuxfoundation/lfx-v2-query-service/gen/query_svc"
"github.com/linuxfoundation/lfx-v2-query-service/internal/domain/port"
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/mock"
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/nats"
"github.com/linuxfoundation/lfx-v2-query-service/internal/infrastructure/opensearch"
logging "github.com/linuxfoundation/lfx-v2-query-service/pkg/log"
"goa.design/clue/debug"
)
Expand Down Expand Up @@ -61,15 +55,16 @@ func main() {
)

// Initialize the resource searcher based on configuration
resourceSearcher := searcherImpl(ctx)
accessControlChecker := accessControlCheckerImpl(ctx)
resourceSearcher := service.SearcherImpl(ctx)
accessControlChecker := service.AccessControlCheckerImpl(ctx)
organizationSearcher := service.OrganizationSearcherImpl(ctx)

// Initialize the services.
var (
querySvcSvc querysvc.Service
)
{
querySvcSvc = querysvcapi.NewQuerySvc(resourceSearcher, accessControlChecker)
querySvcSvc = service.NewQuerySvc(resourceSearcher, accessControlChecker, organizationSearcher)
}

// Wrap the services in endpoints that can be invoked from other services
Expand All @@ -93,7 +88,7 @@ func main() {
ctx, cancel := context.WithCancel(ctx)

// Setup the JWT authentication which validates and parses the JWT token.
querysvcapi.SetupJWTAuth(ctx)
service.SetupJWTAuth(ctx)

// Start the servers and send errors (if any) to the error channel.
addr := ":" + *port
Expand Down Expand Up @@ -141,128 +136,3 @@ func main() {

slog.InfoContext(ctx, "exited")
}

func searcherImpl(ctx context.Context) port.ResourceSearcher {

var (
resourceSearcher port.ResourceSearcher
err error
)

// Search source implementation configuration
searchSource := os.Getenv("SEARCH_SOURCE")
if searchSource == "" {
searchSource = "opensearch"
}

opensearchURL := os.Getenv("OPENSEARCH_URL")
if opensearchURL == "" {
opensearchURL = "http://localhost:9200"
}

opensearchIndex := os.Getenv("OPENSEARCH_INDEX")
if opensearchIndex == "" {
opensearchIndex = "resources"
}

switch searchSource {
case "mock":
slog.InfoContext(ctx, "initializing mock resource searcher")
resourceSearcher = mock.NewMockResourceSearcher()

case "opensearch":
slog.InfoContext(ctx, "initializing opensearch resource searcher",
"url", opensearchURL,
"index", opensearchIndex,
)
opensearchConfig := opensearch.Config{
URL: opensearchURL,
Index: opensearchIndex,
}

resourceSearcher, err = opensearch.NewSearcher(ctx, opensearchConfig)
if err != nil {
log.Fatalf("failed to initialize OpenSearch searcher: %v", err)
}

default:
log.Fatalf("unsupported search implementation: %s", searchSource)
}

return resourceSearcher

}

func accessControlCheckerImpl(ctx context.Context) port.AccessControlChecker {

var (
accessControlChecker port.AccessControlChecker
err error
)

// Access control implementation configuration
accessControlSource := os.Getenv("ACCESS_CONTROL_SOURCE")
if accessControlSource == "" {
accessControlSource = "nats"
}

natsURL := os.Getenv("NATS_URL")
if natsURL == "" {
natsURL = "nats://localhost:4222"
}

natsTimeout := os.Getenv("NATS_TIMEOUT")
if natsTimeout == "" {
natsTimeout = "10s"
}
natsTimeoutDuration, err := time.ParseDuration(natsTimeout)
if err != nil {
log.Fatalf("invalid NATS timeout duration: %v", err)
}

natsMaxReconnect := os.Getenv("NATS_MAX_RECONNECT")
if natsMaxReconnect == "" {
natsMaxReconnect = "3"
}
natsMaxReconnectInt, err := strconv.Atoi(natsMaxReconnect)
if err != nil {
log.Fatalf("invalid NATS max reconnect value %s: %v", natsMaxReconnect, err)
}

natsReconnectWait := os.Getenv("NATS_RECONNECT_WAIT")
if natsReconnectWait == "" {
natsReconnectWait = "2s"
}
natsReconnectWaitDuration, err := time.ParseDuration(natsReconnectWait)
if err != nil {
log.Fatalf("invalid NATS reconnect wait duration %s : %v", natsReconnectWait, err)
}

//natsReconnectWait := flag.Duration("nats-reconnect-wait", 2*time.Second, "NATS reconnection wait time")

// Initialize the access control checker based on configuration
switch accessControlSource {
case "mock":
slog.InfoContext(ctx, "initializing mock access control checker")
accessControlChecker = mock.NewMockAccessControlChecker()

case "nats":
slog.InfoContext(ctx, "initializing NATS access control checker")
natsConfig := nats.Config{
URL: natsURL,
Timeout: natsTimeoutDuration,
MaxReconnect: natsMaxReconnectInt,
ReconnectWait: natsReconnectWaitDuration,
}

accessControlChecker, err = nats.NewAccessControlChecker(ctx, natsConfig)
if err != nil {
log.Fatalf("failed to initialize NATS access control checker: %v", err)
}

default:
log.Fatalf("unsupported access control implementation: %s", accessControlSource)
}

return accessControlChecker
}
Loading