Repository: feast-dev/feast

Issue:
Bug: UserWarning when passing `FeatureService` object to both `apply()` and `get_online_features()` - Caused by difference in `FeatureViewProjection.from_proto()` and `FeatureViewProjection.from_definition()`
## Context

If a `FeatureService` object is created and is being passed to both the `apply()` and the `get_online_features()` method, the following user warning is thrown: 

```
UserWarning: The FeatureService object that has been passed in as an argument is inconsistent with the version from the registry. Potentially a newer version of the FeatureService has been applied to the registry.
```

This is caused by a bug in the creation/retrieval of `feature_view_projections`, which is an attribute of `FeatureService`. An empty string is set to the `name_alias` value of `FeatureViewProjection` when calling [`from_proto`](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/feature_view_projection.py#L52-L64). However, when creating the `FeatureViewProjection` by creating a `FeatureService` object (using the default value) `name_alias` is set to `None`, because it is created with [`FeatureViewProjection.from_definition()`](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/feature_view_projection.py#L66-L73).

The warning is raised [here](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/feature_store.py#L532-L541), because the `feature_service_from_registry` has the empty string value for the underlying `FeatureViewProjection.name_alias` object, while the for `_features` the underlying `FeatureViewProjection.name_alias` is valued `None`.

## Expected Behavior

I would expect that if a `FeatureViewProjection` encapsulated within a `FeatureService` has a `None` value for the `name_alias` attribute when being stored in the feature store, that it would also load with the same `None` value when calling `FeatureViewProjection.from_proto()`.

Then the comparison should also not fail, and the warning should not be thrown.

## Current Behavior

The `FeatureViewProjection` is loaded with an empty string for the value `name_alias` when `FeatureViewProjection.from_proto()` is called. 

This causes the. comparison to fail, and the warning to be thrown. 

## Steps to reproduce

docker-compose.yml
```
---
version: "3"
services:
  db:
    restart: always
    image: postgres:16-alpine
    container_name: feast_db
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=feature_store
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=test
```

feature_store.yaml
```
project: project_name
provider: local
registry:
    registry_type: sql
    path: postgresql://postgres:test@0.0.0.0:5432/feature_store
```

Python script
```
from datetime import timedelta

import pandas as pd
from feast import (
    Entity,
    FeatureService,
    FeatureStore,
    FeatureView,
    Field,
    SnowflakeSource,
    ValueType,
)
from feast.types import Float32

feature_store = FeatureStore()

# Apply
entity = Entity(
    name="entity",
    join_keys=["ID"],
    value_type=ValueType.STRING,
)

source = SnowflakeSource(
    name="source_snowflake",
    timestamp_field="EVENT_TIMESTAMP",
    schema="TEMP",
    table="FEAST_FEATURES",
)

feature_view = FeatureView(
    name="feature_view__v1",
    entities=[entity],
    ttl=timedelta(days=0),
    schema=[
        Field(name="FEATURE", dtype=Float32),
    ],
    online=True,
    source=source,
)

feature_service = FeatureService(
    name="feature_service__v1",
    features=[feature_view],
)

feature_store.apply(
    [
        entity,
        source,
        feature_view,
        feature_service,
    ]
)

# Inference
entity_rows = [{"ID": "ID1"}, {"ID": "ID2"}]
entity_df = pd.DataFrame(entity_rows)
entity_df["event_timestamp"] = pd.to_datetime("now", utc=True)

online_features = feature_store.get_online_features(
    entity_rows=entity_rows,
    features=feature_service,
).to_dict()

print(online_features)
```

### Specifications

Using postgres registry. 

- Version: 0.36.0
- Platform: MacOS - M1
- Subsystem: Sonoma 14.1.1

## Possible Solution

I have little to no experience with proto, so I'm not sure whether it would be possible to load the None value directly from the proto definition.

One potential solution would be to check whether an empty string is loaded for this field when loading from proto, and then set it to None in the `from_proto` function. 



Discussion:
Tracked down the PR from where the mysterious `or ""` originates #2321 Still no idea why that was added, though. Normally, round-trip to and from proto should not change the object (at least as far as `==` operator is concerned), so it does look like a bug.
Thanks for your reply. 

I'll try to open a PR for this :) 
I see there might be some changes required for the `proto` definition as well. I'm however not very familiar with this, and I'm unfortunately not able to look into this right now.. If someone else has the knowledge and time to pick this up soon that would be highly appreciated 🙏 
@job-almekinders my initial though was that simply removing `or ""` from [to_proto](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/feature_view_projection.py#L44) method would be sufficient, but I may be wrong... I'll take a stab at it later if no one picks it up

Resolving patch:
diff --git a/sdk/python/feast/feature_view_projection.py b/sdk/python/feast/feature_view_projection.py
--- a/sdk/python/feast/feature_view_projection.py
+++ b/sdk/python/feast/feature_view_projection.py
@@ -53,7 +53,7 @@ def to_proto(self) -> FeatureViewProjectionProto:
     def from_proto(proto: FeatureViewProjectionProto):
         feature_view_projection = FeatureViewProjection(
             name=proto.feature_view_name,
-            name_alias=proto.feature_view_name_alias,
+            name_alias=proto.feature_view_name_alias or None,
             features=[],
             join_key_map=dict(proto.join_key_map),
             desired_features=[],


Test patch: